From 6175a7fc52af5942bb50362e9d1b31ddcb99f45c Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 28 Aug 2024 06:36:04 +0530 Subject: [PATCH 01/34] feat: add data table hook --- src/hooks/use-data-table.tsx | 48 ++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/hooks/use-data-table.tsx diff --git a/src/hooks/use-data-table.tsx b/src/hooks/use-data-table.tsx new file mode 100644 index 000000000..1faeb5857 --- /dev/null +++ b/src/hooks/use-data-table.tsx @@ -0,0 +1,48 @@ +import { + type ColumnFiltersState, + type RowData, + type SortingState, + type TableOptions, + type VisibilityState, + getCoreRowModel, + getFacetedRowModel, + getFacetedUniqueValues, + getFilteredRowModel, + getPaginationRowModel, + getSortedRowModel, + useReactTable, +} from "@tanstack/react-table"; +import { useState } from "react"; + +type MakeOptional = Omit & Partial>; + +export function useDataTable( + options: MakeOptional, "getCoreRowModel">, +) { + const [sorting, setSorting] = useState([]); + const [columnFilters, setColumnFilters] = useState([]); + const [columnVisibility, setColumnVisibility] = useState({}); + const [rowSelection, setRowSelection] = useState({}); + + return useReactTable({ + ...options, + enableRowSelection: true, + onRowSelectionChange: setRowSelection, + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + onColumnVisibilityChange: setColumnVisibility, + getCoreRowModel: getCoreRowModel(), + getFilteredRowModel: getFilteredRowModel(), + getPaginationRowModel: getPaginationRowModel(), + getSortedRowModel: getSortedRowModel(), + getFacetedRowModel: getFacetedRowModel(), + getFacetedUniqueValues: getFacetedUniqueValues(), + state: { + sorting, + columnFilters, + columnVisibility, + rowSelection, + ...(options?.state && { ...options.state }), + }, + }); +} From 9267b5afa53f93f65415fb74a612587ec29c295c Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 28 Aug 2024 06:37:18 +0530 Subject: [PATCH 02/34] feat: add new data table hook --- src/components/audit/audit-table/index.tsx | 52 +++---------------- src/components/member/member-table.tsx | 42 ++------------- src/components/rbac/role-table.tsx | 39 ++------------ src/components/safe/safe-table/index.tsx | 42 ++------------- .../securities/options/option-table.tsx | 42 ++------------- .../securities/shares/share-table.tsx | 42 ++------------- .../passkey/user-passkeys-data-table.tsx | 52 +++---------------- .../stakeholder/stakeholder-table.tsx | 46 ++-------------- src/components/update/update-table.tsx | 45 ++-------------- 9 files changed, 34 insertions(+), 368 deletions(-) diff --git a/src/components/audit/audit-table/index.tsx b/src/components/audit/audit-table/index.tsx index 87e26a9d4..38ce799c9 100644 --- a/src/components/audit/audit-table/index.tsx +++ b/src/components/audit/audit-table/index.tsx @@ -1,33 +1,18 @@ "use client"; -import * as React from "react"; - -import { - type ColumnDef, - type ColumnFiltersState, - type SortingState, - type VisibilityState, - getCoreRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table"; - -import { Checkbox } from "@/components/ui/checkbox"; - -import { type RouterOutputs } from "@/trpc/shared"; - import { dayjsExt } from "@/common/dayjs"; import { Badge } from "@/components/ui/badge"; +import { Checkbox } from "@/components/ui/checkbox"; import { DataTable } from "@/components/ui/data-table/data-table"; import { DataTableBody } from "@/components/ui/data-table/data-table-body"; import { SortButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableContent } from "@/components/ui/data-table/data-table-content"; import { DataTableHeader } from "@/components/ui/data-table/data-table-header"; import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination"; +import { useDataTable } from "@/hooks/use-data-table"; +import type { RouterOutputs } from "@/trpc/shared"; +import type { ColumnDef } from "@tanstack/react-table"; +import * as React from "react"; import { AuditTableToolbar } from "./audit-table-toolbar"; type Audit = RouterOutputs["audit"]["getAudits"]["data"]; @@ -107,34 +92,9 @@ export const columns: ColumnDef[] = [ ]; export function AuditTable({ audits }: AuditTableProps) { - const [sorting, setSorting] = React.useState([]); - const [columnFilters, setColumnFilters] = React.useState( - [], - ); - const [columnVisibility, setColumnVisibility] = - React.useState({}); - const [rowSelection, setRowSelection] = React.useState({}); - - const table = useReactTable({ + const table = useDataTable({ data: audits, columns: columns, - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - onColumnVisibilityChange: setColumnVisibility, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - state: { - sorting, - columnFilters, - columnVisibility, - rowSelection, - }, }); return ( diff --git a/src/components/member/member-table.tsx b/src/components/member/member-table.tsx index 65ebc7dca..885094967 100644 --- a/src/components/member/member-table.tsx +++ b/src/components/member/member-table.tsx @@ -2,19 +2,7 @@ import * as React from "react"; -import { - type ColumnDef, - type ColumnFiltersState, - type SortingState, - type VisibilityState, - getCoreRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table"; +import type { ColumnDef } from "@tanstack/react-table"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; @@ -31,6 +19,7 @@ import { api } from "@/trpc/react"; import { Avatar, AvatarImage } from "@/components/ui/avatar"; +import { useDataTable } from "@/hooks/use-data-table"; import { getRoleId } from "@/lib/rbac/access-control-utils"; import type { RouterOutputs } from "@/trpc/shared"; import { RiMore2Fill } from "@remixicon/react"; @@ -282,34 +271,9 @@ export const columns: ColumnDef[] = [ ]; const MemberTable = ({ members, roles }: MembersType) => { - const [sorting, setSorting] = React.useState([]); - const [columnFilters, setColumnFilters] = React.useState( - [], - ); - const [columnVisibility, setColumnVisibility] = - React.useState({}); - const [rowSelection, setRowSelection] = React.useState({}); - - const table = useReactTable({ + const table = useDataTable({ data: members, columns: columns, - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - onColumnVisibilityChange: setColumnVisibility, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - state: { - sorting, - columnFilters, - columnVisibility, - rowSelection, - }, meta: { roles, }, diff --git a/src/components/rbac/role-table.tsx b/src/components/rbac/role-table.tsx index 5c5e357d2..216348bd7 100644 --- a/src/components/rbac/role-table.tsx +++ b/src/components/rbac/role-table.tsx @@ -1,23 +1,12 @@ "use client"; +import { useDataTable } from "@/hooks/use-data-table"; import { ADMIN_PERMISSION } from "@/lib/rbac/constants"; import type { TPermission } from "@/lib/rbac/schema"; import { api } from "@/trpc/react"; import type { RouterOutputs } from "@/trpc/shared"; import { RiMore2Fill } from "@remixicon/react"; -import { - type ColumnDef, - type ColumnFiltersState, - type SortingState, - type VisibilityState, - getCoreRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table"; +import type { ColumnDef } from "@tanstack/react-table"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { pushModal } from "../modals"; @@ -197,31 +186,9 @@ function getPermission(permissions_: TPermission[]) { } export function RoleTable({ roles }: RoleTableProps) { - const [sorting, setSorting] = useState([]); - const [columnFilters, setColumnFilters] = useState([]); - const [columnVisibility, setColumnVisibility] = useState({}); - const [rowSelection, setRowSelection] = useState({}); - - const table = useReactTable({ + const table = useDataTable({ data: roles, columns: columns, - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - onColumnVisibilityChange: setColumnVisibility, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - state: { - sorting, - columnFilters, - columnVisibility, - rowSelection, - }, }); return ( diff --git a/src/components/safe/safe-table/index.tsx b/src/components/safe/safe-table/index.tsx index f57ec74bb..d4dfbb31c 100644 --- a/src/components/safe/safe-table/index.tsx +++ b/src/components/safe/safe-table/index.tsx @@ -1,18 +1,6 @@ "use client"; -import { - type ColumnDef, - type ColumnFiltersState, - type SortingState, - type VisibilityState, - getCoreRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table"; +import type { ColumnDef } from "@tanstack/react-table"; import * as React from "react"; import { Button } from "@/components/ui/button"; @@ -35,6 +23,7 @@ import { SortButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableContent } from "@/components/ui/data-table/data-table-content"; import { DataTableHeader } from "@/components/ui/data-table/data-table-header"; import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination"; +import { useDataTable } from "@/hooks/use-data-table"; import { getPresignedGetUrl } from "@/server/file-uploads"; import type { RouterOutputs } from "@/trpc/shared"; import { RiFileDownloadLine, RiMore2Fill } from "@remixicon/react"; @@ -293,34 +282,9 @@ export const columns: ColumnDef[] = [ ]; export const SafeTable = ({ safes }: SafesType) => { - const [sorting, setSorting] = React.useState([]); - const [columnFilters, setColumnFilters] = React.useState( - [], - ); - const [columnVisibility, setColumnVisibility] = - React.useState({}); - const [rowSelection, setRowSelection] = React.useState({}); - - const table = useReactTable({ + const table = useDataTable({ data: safes ?? [], columns: columns, - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - onColumnVisibilityChange: setColumnVisibility, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - state: { - sorting, - columnFilters, - columnVisibility, - rowSelection, - }, }); return ( diff --git a/src/components/securities/options/option-table.tsx b/src/components/securities/options/option-table.tsx index d27612608..7794968f3 100644 --- a/src/components/securities/options/option-table.tsx +++ b/src/components/securities/options/option-table.tsx @@ -1,18 +1,6 @@ "use client"; -import { - type ColumnDef, - type ColumnFiltersState, - type SortingState, - type VisibilityState, - getCoreRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table"; +import type { ColumnDef } from "@tanstack/react-table"; import * as React from "react"; import { Button } from "@/components/ui/button"; @@ -35,6 +23,7 @@ import { SortButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableContent } from "@/components/ui/data-table/data-table-content"; import { DataTableHeader } from "@/components/ui/data-table/data-table-header"; import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination"; +import { useDataTable } from "@/hooks/use-data-table"; import { getPresignedGetUrl } from "@/server/file-uploads"; import type { RouterOutputs } from "@/trpc/shared"; import { RiFileDownloadLine, RiMore2Fill } from "@remixicon/react"; @@ -285,34 +274,9 @@ export const columns: ColumnDef[] = [ ]; const OptionTable = ({ options }: OptionsType) => { - const [sorting, setSorting] = React.useState([]); - const [columnFilters, setColumnFilters] = React.useState( - [], - ); - const [columnVisibility, setColumnVisibility] = - React.useState({}); - const [rowSelection, setRowSelection] = React.useState({}); - - const table = useReactTable({ + const table = useDataTable({ data: options ?? [], columns: columns, - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - onColumnVisibilityChange: setColumnVisibility, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - state: { - sorting, - columnFilters, - columnVisibility, - rowSelection, - }, }); return ( diff --git a/src/components/securities/shares/share-table.tsx b/src/components/securities/shares/share-table.tsx index d511337ab..40dc7e6ee 100644 --- a/src/components/securities/shares/share-table.tsx +++ b/src/components/securities/shares/share-table.tsx @@ -1,18 +1,6 @@ "use client"; -import { - type ColumnDef, - type ColumnFiltersState, - type SortingState, - type VisibilityState, - getCoreRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table"; +import type { ColumnDef } from "@tanstack/react-table"; import * as React from "react"; import { dayjsExt } from "@/common/dayjs"; @@ -34,6 +22,7 @@ import { DropdownMenuLabel, DropdownMenuSeparator, } from "@/components/ui/dropdown-menu"; +import { useDataTable } from "@/hooks/use-data-table"; import { formatCurrency, formatNumber } from "@/lib/utils"; import { getPresignedGetUrl } from "@/server/file-uploads"; import { api } from "@/trpc/react"; @@ -347,34 +336,9 @@ export const columns: ColumnDef[] = [ ]; const ShareTable = ({ shares }: SharesType) => { - const [sorting, setSorting] = React.useState([]); - const [columnFilters, setColumnFilters] = React.useState( - [], - ); - const [columnVisibility, setColumnVisibility] = - React.useState({}); - const [rowSelection, setRowSelection] = React.useState({}); - - const table = useReactTable({ + const table = useDataTable({ data: shares ?? [], columns: columns, - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - onColumnVisibilityChange: setColumnVisibility, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - state: { - sorting, - columnFilters, - columnVisibility, - rowSelection, - }, }); return ( diff --git a/src/components/security/passkey/user-passkeys-data-table.tsx b/src/components/security/passkey/user-passkeys-data-table.tsx index ee09f23be..530a700de 100644 --- a/src/components/security/passkey/user-passkeys-data-table.tsx +++ b/src/components/security/passkey/user-passkeys-data-table.tsx @@ -1,33 +1,20 @@ "use client"; +import { dayjsExt } from "@/common/dayjs"; import { Badge } from "@/components/ui/badge"; +import { DataTable } from "@/components/ui/data-table/data-table"; import { DataTableBody } from "@/components/ui/data-table/data-table-body"; import { SortButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableContent } from "@/components/ui/data-table/data-table-content"; import { DataTableHeader } from "@/components/ui/data-table/data-table-header"; -import type { RouterOutputs } from "@/trpc/shared"; -import * as React from "react"; - -import { - type ColumnDef, - type ColumnFiltersState, - type SortingState, - type VisibilityState, - getCoreRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table"; - -import { dayjsExt } from "@/common/dayjs"; -import { DataTable } from "@/components/ui/data-table/data-table"; import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination"; +import { useDataTable } from "@/hooks/use-data-table"; import { api } from "@/trpc/react"; +import type { RouterOutputs } from "@/trpc/shared"; import { RiMore2Fill } from "@remixicon/react"; +import type { ColumnDef } from "@tanstack/react-table"; import { useRouter } from "next/navigation"; +import * as React from "react"; import { toast } from "sonner"; import { Button } from "../../ui/button"; import { @@ -217,34 +204,9 @@ export const columns: ColumnDef[] = [ ]; const PasskeyTable = ({ passkey }: PasskeyType) => { - const [sorting, setSorting] = React.useState([]); - const [columnFilters, setColumnFilters] = React.useState( - [], - ); - const [columnVisibility, setColumnVisibility] = - React.useState({}); - const [rowSelection, setRowSelection] = React.useState({}); - - const table = useReactTable({ + const table = useDataTable({ data: passkey ?? [], columns: columns, - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - onColumnVisibilityChange: setColumnVisibility, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - state: { - sorting, - columnFilters, - columnVisibility, - rowSelection, - }, }); return ( diff --git a/src/components/stakeholder/stakeholder-table.tsx b/src/components/stakeholder/stakeholder-table.tsx index 5d72cacb8..3709f1ab9 100644 --- a/src/components/stakeholder/stakeholder-table.tsx +++ b/src/components/stakeholder/stakeholder-table.tsx @@ -9,21 +9,10 @@ import { SortButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableContent } from "@/components/ui/data-table/data-table-content"; import { DataTableHeader } from "@/components/ui/data-table/data-table-header"; import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination"; +import { useDataTable } from "@/hooks/use-data-table"; import type { RouterOutputs } from "@/trpc/shared"; import { RiMore2Fill } from "@remixicon/react"; -import { - type ColumnDef, - type ColumnFiltersState, - type SortingState, - type VisibilityState, - getCoreRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table"; +import type { ColumnDef } from "@tanstack/react-table"; import React from "react"; import { Allow } from "../rbac/allow"; import { Button } from "../ui/button"; @@ -218,36 +207,7 @@ export const columns: ColumnDef[] = [ ]; const StakeholderTable = ({ stakeholders }: StakeholderTableType) => { - const [sorting, setSorting] = React.useState([]); - const [columnFilters, setColumnFilters] = React.useState( - [], - ); - const [columnVisibility, setColumnVisibility] = - React.useState({}); - const [rowSelection, setRowSelection] = React.useState({}); - - const table = useReactTable({ - data: stakeholders, - columns: columns, - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - onColumnVisibilityChange: setColumnVisibility, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - state: { - sorting, - columnFilters, - columnVisibility, - rowSelection, - }, - }); - + const table = useDataTable({ columns, data: stakeholders }); return (
diff --git a/src/components/update/update-table.tsx b/src/components/update/update-table.tsx index 15f28b4a9..c920013e2 100644 --- a/src/components/update/update-table.tsx +++ b/src/components/update/update-table.tsx @@ -15,21 +15,10 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; +import { useDataTable } from "@/hooks/use-data-table"; import type { RouterOutputs } from "@/trpc/shared"; import { RiAddCircleLine } from "@remixicon/react"; -import { - type ColumnDef, - type ColumnFiltersState, - type SortingState, - type VisibilityState, - getCoreRowModel, - getFacetedRowModel, - getFacetedUniqueValues, - getFilteredRowModel, - getPaginationRowModel, - getSortedRowModel, - useReactTable, -} from "@tanstack/react-table"; +import type { ColumnDef } from "@tanstack/react-table"; import { useSession } from "next-auth/react"; import Link from "next/link"; import React, { useState } from "react"; @@ -174,35 +163,7 @@ export const columns: ColumnDef[] = [ ]; const UpdateTable = ({ updates }: UpdateTableType) => { - const [sorting, setSorting] = React.useState([]); - const [columnFilters, setColumnFilters] = React.useState( - [], - ); - const [columnVisibility, setColumnVisibility] = - React.useState({}); - const [rowSelection, setRowSelection] = React.useState({}); - - const table = useReactTable({ - data: updates, - columns: columns, - enableRowSelection: true, - onRowSelectionChange: setRowSelection, - onSortingChange: setSorting, - onColumnFiltersChange: setColumnFilters, - onColumnVisibilityChange: setColumnVisibility, - getCoreRowModel: getCoreRowModel(), - getFilteredRowModel: getFilteredRowModel(), - getPaginationRowModel: getPaginationRowModel(), - getSortedRowModel: getSortedRowModel(), - getFacetedRowModel: getFacetedRowModel(), - getFacetedUniqueValues: getFacetedUniqueValues(), - state: { - sorting, - columnFilters, - columnVisibility, - rowSelection, - }, - }); + const table = useDataTable({ data: updates, columns: columns }); return (
From c8ae1ace3f5944a1ddfb07de2fbd871df595dbac Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 28 Aug 2024 06:41:21 +0530 Subject: [PATCH 03/34] chore: rename hook --- .../audit/audit-table/audit-table-toolbar.tsx | 4 ++-- src/components/member/member-table-toolbar.tsx | 4 ++-- .../safe/safe-table/safe-table-toolbar.tsx | 4 ++-- .../securities/options/option-table-toolbar.tsx | 4 ++-- .../securities/shares/share-table-toolbar.tsx | 4 ++-- .../security/passkey/passkey-table-toolbar.tsx | 4 ++-- .../stakeholder/stakeholder-table-toolbar.tsx | 6 ++---- src/components/ui/data-table/data-table-body.tsx | 4 ++-- src/components/ui/data-table/data-table-header.tsx | 4 ++-- .../ui/data-table/data-table-pagination.tsx | 4 ++-- .../ui/data-table/data-table-view-options.tsx | 4 ++-- src/components/ui/data-table/data-table.tsx | 14 +++++++------- src/components/update/update-table-toolbar.tsx | 4 ++-- 13 files changed, 31 insertions(+), 33 deletions(-) diff --git a/src/components/audit/audit-table/audit-table-toolbar.tsx b/src/components/audit/audit-table/audit-table-toolbar.tsx index 9f05b0582..39e8da13f 100644 --- a/src/components/audit/audit-table/audit-table-toolbar.tsx +++ b/src/components/audit/audit-table/audit-table-toolbar.tsx @@ -1,11 +1,11 @@ -import { useDataTable } from "@/components/ui/data-table/data-table"; +import { useTable } from "@/components/ui/data-table/data-table"; import { ResetButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableFacetedFilter } from "@/components/ui/data-table/data-table-faceted-filter"; import { DataTableViewOptions } from "@/components/ui/data-table/data-table-view-options"; import { getActions } from "@/server/audit/schema"; export function AuditTableToolbar() { - const { table } = useDataTable(); + const { table } = useTable(); const isFiltered = table.getState().columnFilters.length > 0; const actions = getActions(); diff --git a/src/components/member/member-table-toolbar.tsx b/src/components/member/member-table-toolbar.tsx index d9a045222..e4a57ba47 100644 --- a/src/components/member/member-table-toolbar.tsx +++ b/src/components/member/member-table-toolbar.tsx @@ -1,4 +1,4 @@ -import { useDataTable } from "../ui/data-table/data-table"; +import { useTable } from "../ui/data-table/data-table"; import { ResetButton } from "../ui/data-table/data-table-buttons"; import { DataTableFacetedFilter } from "../ui/data-table/data-table-faceted-filter"; import { DataTableViewOptions } from "../ui/data-table/data-table-view-options"; @@ -6,7 +6,7 @@ import { Input } from "../ui/input"; import { statusValues } from "./data"; export function MemberTableToolbar() { - const { table } = useDataTable(); + const { table } = useTable(); const isFiltered = table.getState().columnFilters.length > 0; return ( diff --git a/src/components/safe/safe-table/safe-table-toolbar.tsx b/src/components/safe/safe-table/safe-table-toolbar.tsx index d011eff02..b84e26c83 100644 --- a/src/components/safe/safe-table/safe-table-toolbar.tsx +++ b/src/components/safe/safe-table/safe-table-toolbar.tsx @@ -1,4 +1,4 @@ -import { useDataTable } from "@/components/ui/data-table/data-table"; +import { useTable } from "@/components/ui/data-table/data-table"; import { ResetButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableFacetedFilter } from "@/components/ui/data-table/data-table-faceted-filter"; import { DataTableViewOptions } from "@/components/ui/data-table/data-table-view-options"; @@ -6,7 +6,7 @@ import { Input } from "@/components/ui/input"; import { statusValues } from "./data"; export function SafeTableToolbar() { - const { table } = useDataTable(); + const { table } = useTable(); const isFiltered = table.getState().columnFilters.length > 0; return ( diff --git a/src/components/securities/options/option-table-toolbar.tsx b/src/components/securities/options/option-table-toolbar.tsx index 09dca0dc3..81d391532 100644 --- a/src/components/securities/options/option-table-toolbar.tsx +++ b/src/components/securities/options/option-table-toolbar.tsx @@ -1,4 +1,4 @@ -import { useDataTable } from "@/components/ui/data-table/data-table"; +import { useTable } from "@/components/ui/data-table/data-table"; import { ResetButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableFacetedFilter } from "@/components/ui/data-table/data-table-faceted-filter"; import { DataTableViewOptions } from "@/components/ui/data-table/data-table-view-options"; @@ -6,7 +6,7 @@ import { Input } from "@/components/ui/input"; import { statusValues } from "./data"; export function OptionTableToolbar() { - const { table } = useDataTable(); + const { table } = useTable(); const isFiltered = table.getState().columnFilters.length > 0; return ( diff --git a/src/components/securities/shares/share-table-toolbar.tsx b/src/components/securities/shares/share-table-toolbar.tsx index 7836bd58c..ea636e4c4 100644 --- a/src/components/securities/shares/share-table-toolbar.tsx +++ b/src/components/securities/shares/share-table-toolbar.tsx @@ -1,4 +1,4 @@ -import { useDataTable } from "@/components/ui/data-table/data-table"; +import { useTable } from "@/components/ui/data-table/data-table"; import { ResetButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableFacetedFilter } from "@/components/ui/data-table/data-table-faceted-filter"; import { DataTableViewOptions } from "@/components/ui/data-table/data-table-view-options"; @@ -6,7 +6,7 @@ import { Input } from "@/components/ui/input"; import { statusValues } from "./data"; export function ShareTableToolbar() { - const { table } = useDataTable(); + const { table } = useTable(); const isFiltered = table.getState().columnFilters.length > 0; return ( diff --git a/src/components/security/passkey/passkey-table-toolbar.tsx b/src/components/security/passkey/passkey-table-toolbar.tsx index 74f0d24ac..160a3a502 100644 --- a/src/components/security/passkey/passkey-table-toolbar.tsx +++ b/src/components/security/passkey/passkey-table-toolbar.tsx @@ -1,4 +1,4 @@ -import { useDataTable } from "@/components/ui/data-table/data-table"; +import { useTable } from "@/components/ui/data-table/data-table"; import { ResetButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableFacetedFilter } from "@/components/ui/data-table/data-table-faceted-filter"; import { DataTableViewOptions } from "@/components/ui/data-table/data-table-view-options"; @@ -6,7 +6,7 @@ import { Input } from "@/components/ui/input"; import { statusValues } from "./data"; export function PasskeyTableToolbar() { - const { table } = useDataTable(); + const { table } = useTable(); const isFiltered = table.getState().columnFilters.length > 0; return ( diff --git a/src/components/stakeholder/stakeholder-table-toolbar.tsx b/src/components/stakeholder/stakeholder-table-toolbar.tsx index 4e57ff3e3..cf1d66074 100644 --- a/src/components/stakeholder/stakeholder-table-toolbar.tsx +++ b/src/components/stakeholder/stakeholder-table-toolbar.tsx @@ -1,12 +1,10 @@ -import { statusValues } from "@/components/member/data"; -import { useDataTable } from "@/components/ui/data-table/data-table"; +import { useTable } from "@/components/ui/data-table/data-table"; import { ResetButton } from "@/components/ui/data-table/data-table-buttons"; -import { DataTableFacetedFilter } from "@/components/ui/data-table/data-table-faceted-filter"; import { DataTableViewOptions } from "@/components/ui/data-table/data-table-view-options"; import { Input } from "@/components/ui/input"; export const StakeholderTableToolbar = () => { - const { table } = useDataTable(); + const { table } = useTable(); const isFiltered = table.getState().columnFilters.length > 0; return ( diff --git a/src/components/ui/data-table/data-table-body.tsx b/src/components/ui/data-table/data-table-body.tsx index 1323bc106..9a77727ba 100644 --- a/src/components/ui/data-table/data-table-body.tsx +++ b/src/components/ui/data-table/data-table-body.tsx @@ -1,9 +1,9 @@ import { flexRender } from "@tanstack/react-table"; import { TableBody, TableCell, TableRow } from "../table"; -import { useDataTable } from "./data-table"; +import { useTable } from "./data-table"; export function DataTableBody() { - const { table } = useDataTable(); + const { table } = useTable(); const columnLength = table.getAllColumns().length; diff --git a/src/components/ui/data-table/data-table-header.tsx b/src/components/ui/data-table/data-table-header.tsx index 34cf2de31..5e37eaaa9 100644 --- a/src/components/ui/data-table/data-table-header.tsx +++ b/src/components/ui/data-table/data-table-header.tsx @@ -1,10 +1,10 @@ import { flexRender } from "@tanstack/react-table"; import { TableHead, TableHeader, TableRow } from "../table"; -import { useDataTable } from "./data-table"; +import { useTable } from "./data-table"; export function DataTableHeader() { - const { table } = useDataTable(); + const { table } = useTable(); return ( {table.getHeaderGroups().map((headerGroup) => ( diff --git a/src/components/ui/data-table/data-table-pagination.tsx b/src/components/ui/data-table/data-table-pagination.tsx index 8e170fbe6..1e47a60a5 100644 --- a/src/components/ui/data-table/data-table-pagination.tsx +++ b/src/components/ui/data-table/data-table-pagination.tsx @@ -1,8 +1,8 @@ import { Button } from "../button"; -import { useDataTable } from "./data-table"; +import { useTable } from "./data-table"; export function DataTablePagination() { - const { table } = useDataTable(); + const { table } = useTable(); return (
diff --git a/src/components/ui/data-table/data-table-view-options.tsx b/src/components/ui/data-table/data-table-view-options.tsx index bb9703714..ac9b71d6a 100644 --- a/src/components/ui/data-table/data-table-view-options.tsx +++ b/src/components/ui/data-table/data-table-view-options.tsx @@ -9,10 +9,10 @@ import { } from "../dropdown-menu"; import { RiArrowDownSLine } from "@remixicon/react"; -import { useDataTable } from "./data-table"; +import { useTable } from "./data-table"; export function DataTableViewOptions() { - const { table } = useDataTable(); + const { table } = useTable(); return ( diff --git a/src/components/ui/data-table/data-table.tsx b/src/components/ui/data-table/data-table.tsx index dced417f7..5b9af7fbf 100644 --- a/src/components/ui/data-table/data-table.tsx +++ b/src/components/ui/data-table/data-table.tsx @@ -1,6 +1,6 @@ import { type ReactNode, createContext, useContext } from "react"; -import { type Table } from "@tanstack/react-table"; +import type { Table } from "@tanstack/react-table"; interface DataTableRootProps { children: ReactNode; @@ -12,13 +12,13 @@ interface TDataTableContext { } // biome-ignore lint/suspicious/noExplicitAny: -const dataTableContext = createContext | null>(null); +const TableStateContext = createContext | null>(null); -export const useDataTable = () => { - const context = useContext(dataTableContext); +export const useTable = () => { + const context = useContext(TableStateContext); if (!context) { - throw new Error("useDataTable should be called inside DataTable"); + throw new Error("useTable should be called inside DataTable"); } return context; @@ -29,8 +29,8 @@ export function DataTable({ table, }: DataTableRootProps) { return ( - + {children} - + ); } diff --git a/src/components/update/update-table-toolbar.tsx b/src/components/update/update-table-toolbar.tsx index f7b8fb042..647c92e44 100644 --- a/src/components/update/update-table-toolbar.tsx +++ b/src/components/update/update-table-toolbar.tsx @@ -1,10 +1,10 @@ -import { useDataTable } from "@/components/ui/data-table/data-table"; +import { useTable } from "@/components/ui/data-table/data-table"; import { ResetButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableViewOptions } from "@/components/ui/data-table/data-table-view-options"; import { Input } from "@/components/ui/input"; export const UpdateTableToolbar = () => { - const { table } = useDataTable(); + const { table } = useTable(); const isFiltered = table.getState().columnFilters.length > 0; return ( From a0106f4e83886e2ee95d49669dbbed077ad35525 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 28 Aug 2024 07:01:31 +0530 Subject: [PATCH 04/34] feat: add paginated hook --- src/hooks/use-data-table.tsx | 7 ++++++- src/hooks/use-paginated-data-table.ts | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+), 1 deletion(-) create mode 100644 src/hooks/use-paginated-data-table.ts diff --git a/src/hooks/use-data-table.tsx b/src/hooks/use-data-table.tsx index 1faeb5857..82b458167 100644 --- a/src/hooks/use-data-table.tsx +++ b/src/hooks/use-data-table.tsx @@ -16,8 +16,13 @@ import { useState } from "react"; type MakeOptional = Omit & Partial>; +export type TDataTableOptions = MakeOptional< + TableOptions, + "getCoreRowModel" +>; + export function useDataTable( - options: MakeOptional, "getCoreRowModel">, + options: TDataTableOptions, ) { const [sorting, setSorting] = useState([]); const [columnFilters, setColumnFilters] = useState([]); diff --git a/src/hooks/use-paginated-data-table.ts b/src/hooks/use-paginated-data-table.ts new file mode 100644 index 000000000..f52c6f9a7 --- /dev/null +++ b/src/hooks/use-paginated-data-table.ts @@ -0,0 +1,18 @@ +import type { RowData, TableState } from "@tanstack/react-table"; +import { type TDataTableOptions, useDataTable } from "./use-data-table"; + +type MakeRequired = Omit & Required>; + +type TState = MakeRequired, "pagination">; + +export function usePaginatedTable( + options: Omit, "state"> & { state: TState }, +) { + return useDataTable({ + ...options, + manualPagination: true, + manualSorting: true, + manualFiltering: true, + pageCount: options.pageCount ?? -1, + }); +} From 99ff880bb8cda2a886a3b4769cba8ab2f625cd84 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 28 Aug 2024 08:44:26 +0530 Subject: [PATCH 05/34] feat: add offset schema --- src/server/api/schema/pagination.ts | 73 +++++++++++++++++++++++++++-- 1 file changed, 70 insertions(+), 3 deletions(-) diff --git a/src/server/api/schema/pagination.ts b/src/server/api/schema/pagination.ts index 36e608c72..3dc4e3455 100644 --- a/src/server/api/schema/pagination.ts +++ b/src/server/api/schema/pagination.ts @@ -1,5 +1,40 @@ import { z } from "@hono/zod-openapi"; -import { DEFAULT_PAGINATION_LIMIT } from "../const"; +import { DEFAULT_PAGINATION_LIMIT, OFFSET_MAXIMUM_LIMIT } from "../const"; + +export const OffsetPaginationQuerySchema = z.object({ + limit: z.coerce + .number() + .max(OFFSET_MAXIMUM_LIMIT) + .positive() + .optional() + .default(DEFAULT_PAGINATION_LIMIT) + .openapi({ + description: `The number of items to return per page. The maximum value is ${OFFSET_MAXIMUM_LIMIT}.`, + param: { + name: "limit", + in: "query", + }, + example: DEFAULT_PAGINATION_LIMIT, + default: DEFAULT_PAGINATION_LIMIT, + maximum: OFFSET_MAXIMUM_LIMIT, + minimum: 1, + }), + page: z.coerce + .number() + .positive() + .optional() + .default(1) + .openapi({ + description: "The page number to retrieve. Starts from 1.", + param: { + name: "page", + in: "query", + }, + example: 1, + default: 1, + minimum: 1, + }), +}); export const PaginationQuerySchema = z.object({ limit: z @@ -53,7 +88,6 @@ export const PaginationResponseSchema = z hasNextPage: z.boolean().openapi({ description: "Indicates if there is a next page available in the pagination. `true` if there are more pages after the current one, `false` otherwise.", - example: false, }), startCursor: z.string().nullable().openapi({ @@ -68,4 +102,37 @@ export const PaginationResponseSchema = z example: "cly151kxq0000i7ngb3erchgo", }), }) - .openapi("Pagination"); + .openapi("Pagination Cursor"); + +export const OffsetPaginationResponseSchema = z + .object({ + isFirstPage: z.boolean().openapi({ + description: + "Indicates whether the current page is the first page of the pagination.", + }), + isLastPage: z.boolean().openapi({ + description: + "Indicates whether the current page is the last page of the pagination.", + }), + currentPage: z.number().openapi({ + description: "The current page number in the pagination.", + }), + previousPage: z.number().nullable().openapi({ + description: + "The previous page number in the pagination. Null if the current page is the first page.", + }), + nextPage: z.number().nullable().openapi({ + description: + "The next page number in the pagination. Null if the current page is the last page.", + }), + pageCount: z.number().openapi({ + description: "The total number of pages available.", + }), + totalCount: z.number().openapi({ + description: "The total number of items across all pages.", + }), + }) + .openapi("Offset Pagination", { + description: + "A schema representing the pagination details of an offset-based pagination system.", + }); From 07f2d551e46cfa8ca2b691da9db366209db9c920 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 28 Aug 2024 08:44:43 +0530 Subject: [PATCH 06/34] feat: use offset schema --- src/server/api/routes/stakeholder/getMany.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/server/api/routes/stakeholder/getMany.ts b/src/server/api/routes/stakeholder/getMany.ts index c34554d41..e183f308c 100644 --- a/src/server/api/routes/stakeholder/getMany.ts +++ b/src/server/api/routes/stakeholder/getMany.ts @@ -1,14 +1,14 @@ import { z } from "@hono/zod-openapi"; import { - PaginationQuerySchema, - PaginationResponseSchema, + OffsetPaginationQuerySchema, + OffsetPaginationResponseSchema, } from "../../schema/pagination"; import { StakeholderSchema } from "../../schema/stakeholder"; import { authMiddleware, withAuthApiV1 } from "../../utils/endpoint-creator"; const ResponseSchema = z.object({ data: z.array(StakeholderSchema), - meta: PaginationResponseSchema, + meta: OffsetPaginationResponseSchema, }); const ParamsSchema = z.object({ @@ -33,7 +33,7 @@ export const getMany = withAuthApiV1 path: "/v1/{companyId}/stakeholders", middleware: [authMiddleware()], request: { - query: PaginationQuerySchema, + query: OffsetPaginationQuerySchema, params: ParamsSchema, }, responses: { @@ -54,9 +54,9 @@ export const getMany = withAuthApiV1 const [data, meta] = await db.stakeholder .paginate({ where: { companyId: membership.companyId } }) - .withCursor({ - limit: query.limit, - after: query.cursor, + .withPages({ + includePageCount: true, + ...query, }); const response: z.infer = { From 5c8a3d3560ee1b434df7951c07956dc5a57ee651 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 28 Aug 2024 08:44:55 +0530 Subject: [PATCH 07/34] fix: session --- src/server/api/middlewares/session-token.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/server/api/middlewares/session-token.ts b/src/server/api/middlewares/session-token.ts index 4cc0ecf89..470f2ec57 100644 --- a/src/server/api/middlewares/session-token.ts +++ b/src/server/api/middlewares/session-token.ts @@ -74,7 +74,6 @@ async function fetchSessionFromAuthUrl( new Request(newUrl, { method: "GET", headers: clonedRequest.headers, - body: clonedRequest.body, }), ); From 1238f7150bcc95f3aee03d35bd6622bc7b57c867 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 28 Aug 2024 08:45:11 +0530 Subject: [PATCH 08/34] feat: add const --- src/server/api/const.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/server/api/const.ts b/src/server/api/const.ts index 86b6666b0..627c17186 100644 --- a/src/server/api/const.ts +++ b/src/server/api/const.ts @@ -1,2 +1,3 @@ -export const DEFAULT_PAGINATION_LIMIT = 50; +export const DEFAULT_PAGINATION_LIMIT = 10 as const; +export const OFFSET_MAXIMUM_LIMIT = 50 as const; export const SECURITY_SCHEME_NAME = "Bearer"; From 612238ad9424ed86f5b962eb99a798b8f76b8ae1 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 28 Aug 2024 08:45:27 +0530 Subject: [PATCH 09/34] chore: fix error handling --- src/server/api/error.ts | 41 +++++++++++++++++++++++++---------------- 1 file changed, 25 insertions(+), 16 deletions(-) diff --git a/src/server/api/error.ts b/src/server/api/error.ts index 685b9c006..c960a316e 100644 --- a/src/server/api/error.ts +++ b/src/server/api/error.ts @@ -200,11 +200,14 @@ export class ApiError extends HTTPException { export function handleError(err: Error, c: Context): Response { if (err instanceof ApiError) { if (err.status >= 500) { - log.error("ApiError", { - name: err.name, - code: err.code, - status: err.status, - }); + log.error( + { + name: err.name, + code: err.code, + status: err.status, + }, + "ApiError", + ); } return c.json( { @@ -219,11 +222,14 @@ export function handleError(err: Error, c: Context): Response { if (err instanceof HTTPException) { if (err.status >= 500) { - log.error("HTTPException", { - name: err.name, - status: err.status, - message: err.message, - }); + log.error( + { + name: err.name, + status: err.status, + message: err.message, + }, + "HTTPException", + ); } const code = statusToCode(err.status); return c.json( @@ -241,12 +247,15 @@ export function handleError(err: Error, c: Context): Response { * This is a generic error, we should log it and return a 500 */ - log.error("UnhandledError", { - name: err.name, - message: err.message, - cause: err.cause, - stack: err.stack, - }); + log.error( + { + name: err.name, + message: err.message, + cause: err.cause, + stack: err.stack, + }, + "UnhandledError", + ); return c.json( { From 733b70d5124bf4bd136156e644f0eb75a01407b3 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 28 Aug 2024 16:23:31 +0530 Subject: [PATCH 10/34] chore: make header optional --- src/server/api/utils/endpoint-creator.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/server/api/utils/endpoint-creator.ts b/src/server/api/utils/endpoint-creator.ts index d22d9f158..592914bf2 100644 --- a/src/server/api/utils/endpoint-creator.ts +++ b/src/server/api/utils/endpoint-creator.ts @@ -28,7 +28,8 @@ const AuthHeaderSchema = z.object({ .openapi({ description: "Bearer token to authorize the request", example: "Bearer api_x0X0x0X0x0X0x0X0x0X0x0X", - }), + }) + .optional(), }); type AuthHeaders = { @@ -92,7 +93,7 @@ const createApi = ( }; export const authMiddleware = (option?: accessTokenAuthMiddlewareOptions) => - some(sessionCookieAuthMiddleware(), accessTokenAuthMiddleware(option)); + some(accessTokenAuthMiddleware(option), sessionCookieAuthMiddleware()); export const ApiV1 = createApi("v1"); From 36c9a726278686ead853622bdb882c4f70063df4 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 28 Aug 2024 16:23:53 +0530 Subject: [PATCH 11/34] feat: add api client --- src/server/api/api-client.ts | 136 ++++++++++++++++++ src/server/api/client-handlers/stakeholder.ts | 18 +++ 2 files changed, 154 insertions(+) create mode 100644 src/server/api/api-client.ts create mode 100644 src/server/api/client-handlers/stakeholder.ts diff --git a/src/server/api/api-client.ts b/src/server/api/api-client.ts new file mode 100644 index 000000000..fd7f77853 --- /dev/null +++ b/src/server/api/api-client.ts @@ -0,0 +1,136 @@ +import { env } from "@/env"; +import type { + RouteConfigToTypedResponse, + createRoute, + z, +} from "@hono/zod-openapi"; + +// biome-ignore lint/suspicious/noExplicitAny: +type ExtractParams = T extends { params: z.ZodType } + ? z.infer + : never; +// biome-ignore lint/suspicious/noExplicitAny: +type ExtractQuery = T extends { query: z.ZodType } + ? z.infer + : never; + +type ExtractRequestBody = T extends { + body: { + content: { + "application/json": { + // biome-ignore lint/suspicious/noExplicitAny: + schema: z.ZodType; + }; + }; + }; +} + ? z.infer + : never; + +// biome-ignore lint/complexity/noBannedTypes: +type IfNever = [T] extends [never] ? {} : Obj; + +export type APIClientParams = IfNever< + ExtractParams, + { urlParams: ExtractParams } +> & + IfNever< + ExtractQuery, + { searchParams: ExtractQuery } + > & + IfNever< + ExtractRequestBody, + { json: ExtractRequestBody } + > & { + headers?: Headers; + }; + +type RouteConfig = Parameters[0]; + +export async function createClient( + method: U["method"], + url: U["path"], + params: APIClientParams, +) { + try { + const path = buildPath(url, params); + const headers = buildHeaders(params.headers); + const requestOptions = buildRequestOptions(method, headers, params); + + const response = await fetch(path, requestOptions); + + if (!response.ok) { + const errorMessage = await getErrorMessage(response); + throw new Error(`HTTP Error: ${response.status} - ${errorMessage}`); + } + + try { + return response.json() as RouteConfigToTypedResponse["_data"]; + } catch (_jsonError) { + throw new Error("Failed to parse JSON response."); + } + } catch (error) { + console.error("Error in createClient:", error); + throw error; + } +} + +function interpolatePath( + path: string, + params: Record, +): string { + return path.replace(/{([^}]+)}/g, (_, key) => + encodeURIComponent(String(params[key])), + ); +} + +function buildHeaders(customHeaders?: HeadersInit): Headers { + const headers = new Headers(customHeaders); + headers.set("Content-Type", "application/json"); + return headers; +} + +function buildPath( + url: string, + params: APIClientParams, +): string { + let path = interpolatePath( + `${env.NEXT_PUBLIC_BASE_URL}/api${url}`, + "urlParams" in params ? params.urlParams : {}, + ); + + if ("searchParams" in params) { + const queryString = new URLSearchParams(params.searchParams).toString(); + path += `?${queryString}`; + } + + return path; +} + +function buildRequestOptions( + method: string, + headers: Headers, + params: APIClientParams, +): RequestInit { + const requestOptions: RequestInit = { + method: method.toUpperCase(), + credentials: "include", + headers, + cache: "no-store", + }; + + if ("json" in params) { + requestOptions.body = JSON.stringify(params.json); + } + + return requestOptions; +} + +async function getErrorMessage(response: Response): Promise { + try { + const data = await response.json(); + return data.message || "Unknown error occurred."; + } catch { + return response.statusText || "Unknown error occurred."; + } +} diff --git a/src/server/api/client-handlers/stakeholder.ts b/src/server/api/client-handlers/stakeholder.ts new file mode 100644 index 000000000..48080bac6 --- /dev/null +++ b/src/server/api/client-handlers/stakeholder.ts @@ -0,0 +1,18 @@ +import { type APIClientParams, createClient } from "../api-client"; + +import type { getMany } from "../routes/stakeholder/getMany"; + +type GetManyRoute = typeof getMany.route; + +export const getManyStakeholder = (params: TGetManyStakeholderParams) => { + return createClient( + "get", + "/v1/{companyId}/stakeholders", + params, + ); +}; + +export type TGetManyStakeholderParams = APIClientParams; +export type TGetManyStakeholderRes = Awaited< + ReturnType +>; From bcede0d0fd9912050051d99e3203670adb2c9083 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 28 Aug 2024 22:37:37 +0530 Subject: [PATCH 12/34] chore: add nuqs --- package.json | 1 + pnpm-lock.yaml | 13 +++++++++++++ 2 files changed, 14 insertions(+) diff --git a/package.json b/package.json index 70b54111b..81a6ebc05 100644 --- a/package.json +++ b/package.json @@ -94,6 +94,7 @@ "next-auth": "^4.24.7", "next-nprogress-bar": "^2.3.11", "nodemailer": "^6.9.14", + "nuqs": "^1.17.8", "papaparse": "^5.4.1", "pdf-lib": "^1.17.1", "pg-boss": "^9.0.3", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a849666b1..dd493d804 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -200,6 +200,9 @@ importers: nodemailer: specifier: ^6.9.14 version: 6.9.14 + nuqs: + specifier: ^1.17.8 + version: 1.17.8(next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1)) papaparse: specifier: ^5.4.1 version: 5.4.1 @@ -6617,6 +6620,11 @@ packages: nprogress@0.2.0: resolution: {integrity: sha512-I19aIingLgR1fmhftnbWWO3dXc0hSxqHQHQb3H8m+K3TnEn/iSeTZZOyvKXWqQESMwuUVnatlCnZdLBZZt2VSA==} + nuqs@1.17.8: + resolution: {integrity: sha512-JqsnzO+hJyjJE7ebuhpHMLA2iGY48e2xr0oJQFhj7kjUmDABL2XOup47rxF5TL/5b9jEsmU2t0lAKin1VdK1/A==} + peerDependencies: + next: '>=13.4 <14.0.2 || ^14.0.3' + oauth@0.9.15: resolution: {integrity: sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==} @@ -16703,6 +16711,11 @@ snapshots: nprogress@0.2.0: {} + nuqs@1.17.8(next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1)): + dependencies: + mitt: 3.0.1 + next: 14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + oauth@0.9.15: {} object-assign@4.1.1: {} From 399b2220180d77beb7f72e2ed40834b71012f7d9 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Wed, 28 Aug 2024 22:38:41 +0530 Subject: [PATCH 13/34] feat: use pagination --- .../[publicId]/stakeholders/page.tsx | 8 +++- .../stakeholder/single-stake-holder-form.tsx | 4 +- .../stakeholder/stakeholder-table.tsx | 48 ++++++++++++++++--- src/hooks/use-paginated-data-table.ts | 8 ++++ src/server/api/client-hooks/stakeholder.ts | 18 +++++++ 5 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 src/server/api/client-hooks/stakeholder.ts diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/stakeholders/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/stakeholders/page.tsx index e2d358d57..fbf95c50e 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/stakeholders/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/stakeholders/page.tsx @@ -4,6 +4,7 @@ import StakeholderTable from "@/components/stakeholder/stakeholder-table"; import { Card } from "@/components/ui/card"; import { UnAuthorizedState } from "@/components/ui/un-authorized-state"; import { serverAccessControl } from "@/lib/rbac/access-control"; +import { withServerSession } from "@/server/auth"; import { api } from "@/trpc/server"; import { RiGroup2Fill } from "@remixicon/react"; import type { Metadata } from "next"; @@ -13,6 +14,8 @@ export const metadata: Metadata = { }; const StakeholdersPage = async () => { + const session = await withServerSession(); + const { allow } = await serverAccessControl(); const stakeholders = await allow(api.stakeholder.getStakeholders.query(), [ "stakeholder", @@ -55,7 +58,10 @@ const StakeholdersPage = async () => {
- +
); diff --git a/src/components/modals/stakeholder/single-stake-holder-form.tsx b/src/components/modals/stakeholder/single-stake-holder-form.tsx index f49147992..be8468db3 100644 --- a/src/components/modals/stakeholder/single-stake-holder-form.tsx +++ b/src/components/modals/stakeholder/single-stake-holder-form.tsx @@ -23,13 +23,13 @@ import { StakeholderRelationshipEnum, StakeholderTypeEnum, } from "@/prisma/enums"; +import type { TGetManyStakeholderRes } from "@/server/api/client-handlers/stakeholder"; import { api } from "@/trpc/react"; import type { RouterOutputs } from "@/trpc/shared"; import { useRouter } from "next/navigation"; import { toast } from "sonner"; -export type TStakeholder = - RouterOutputs["stakeholder"]["getStakeholders"][number]; +export type TStakeholder = TGetManyStakeholderRes["data"][number]; type TSingleStakeholderForm = | { diff --git a/src/components/stakeholder/stakeholder-table.tsx b/src/components/stakeholder/stakeholder-table.tsx index 3709f1ab9..b3189a864 100644 --- a/src/components/stakeholder/stakeholder-table.tsx +++ b/src/components/stakeholder/stakeholder-table.tsx @@ -9,11 +9,16 @@ import { SortButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableContent } from "@/components/ui/data-table/data-table-content"; import { DataTableHeader } from "@/components/ui/data-table/data-table-header"; import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination"; -import { useDataTable } from "@/hooks/use-data-table"; +import { + usePaginatedQueryParams, + usePaginatedTable, +} from "@/hooks/use-paginated-data-table"; +import type { TGetManyStakeholderRes } from "@/server/api/client-handlers/stakeholder"; +import { useManyStakeholder } from "@/server/api/client-hooks/stakeholder"; import type { RouterOutputs } from "@/trpc/shared"; import { RiMore2Fill } from "@remixicon/react"; -import type { ColumnDef } from "@tanstack/react-table"; -import React from "react"; +import type { ColumnDef, PaginationState } from "@tanstack/react-table"; +import { use, useState } from "react"; import { Allow } from "../rbac/allow"; import { Button } from "../ui/button"; import { @@ -30,6 +35,7 @@ type Stakeholder = RouterOutputs["stakeholder"]["getStakeholders"]; type StakeholderTableType = { stakeholders: Stakeholder; + companyId: string; }; const getStakeholderType = (type: string) => { @@ -72,7 +78,7 @@ const getCurrentRelationship = (relationship: string) => { } }; -export const columns: ColumnDef[] = [ +export const columns: ColumnDef[] = [ { id: "select", header: ({ table }) => ( @@ -206,8 +212,38 @@ export const columns: ColumnDef[] = [ }, ]; -const StakeholderTable = ({ stakeholders }: StakeholderTableType) => { - const table = useDataTable({ columns, data: stakeholders }); +const StakeholderTable = ({ companyId }: StakeholderTableType) => { + const [{ limit, page }, setData] = usePaginatedQueryParams(); + + const pageIndex = page - 1; + const pageSize = limit; + + const { data } = useManyStakeholder({ + searchParams: { limit, page }, + urlParams: { companyId }, + }); + + const table = usePaginatedTable({ + pageCount: data?.meta.pageCount ?? -1, + columns, + data: data?.data ?? [], + state: { + pagination: { + pageIndex, + pageSize, + }, + }, + onPaginationChange: (updater) => { + if (typeof updater === "function") { + const updateValue = updater({ pageIndex, pageSize }); + + setData({ + limit: updateValue.pageSize, + page: updateValue.pageIndex + 1, + }); + } + }, + }); return (
diff --git a/src/hooks/use-paginated-data-table.ts b/src/hooks/use-paginated-data-table.ts index f52c6f9a7..312422dcc 100644 --- a/src/hooks/use-paginated-data-table.ts +++ b/src/hooks/use-paginated-data-table.ts @@ -1,10 +1,18 @@ import type { RowData, TableState } from "@tanstack/react-table"; +import { parseAsInteger, useQueryStates } from "nuqs"; import { type TDataTableOptions, useDataTable } from "./use-data-table"; type MakeRequired = Omit & Required>; type TState = MakeRequired, "pagination">; +export function usePaginatedQueryParams() { + return useQueryStates({ + page: parseAsInteger.withDefault(1), + limit: parseAsInteger.withDefault(2), + }); +} + export function usePaginatedTable( options: Omit, "state"> & { state: TState }, ) { diff --git a/src/server/api/client-hooks/stakeholder.ts b/src/server/api/client-hooks/stakeholder.ts new file mode 100644 index 000000000..7f8605d9a --- /dev/null +++ b/src/server/api/client-hooks/stakeholder.ts @@ -0,0 +1,18 @@ +"use client"; + +import { useQuery } from "@tanstack/react-query"; +import { + type TGetManyStakeholderParams, + getManyStakeholder, +} from "../client-handlers/stakeholder"; + +export const useManyStakeholder = (data: TGetManyStakeholderParams) => + useQuery({ + queryKey: [ + "all-stakeholder", + data.urlParams.companyId, + String(data.searchParams.limit), + String(data.searchParams.page), + ], + queryFn: () => getManyStakeholder(data), + }); From b207ff08a35c4d5fdf25de52c36787869beace1a Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Thu, 29 Aug 2024 13:59:21 +0530 Subject: [PATCH 14/34] refactor: hook --- .../stakeholder/stakeholder-table.tsx | 18 +++-------- src/hooks/use-paginated-data-table.ts | 31 +++++++++++++++++-- 2 files changed, 32 insertions(+), 17 deletions(-) diff --git a/src/components/stakeholder/stakeholder-table.tsx b/src/components/stakeholder/stakeholder-table.tsx index b3189a864..644223636 100644 --- a/src/components/stakeholder/stakeholder-table.tsx +++ b/src/components/stakeholder/stakeholder-table.tsx @@ -12,6 +12,7 @@ import { DataTablePagination } from "@/components/ui/data-table/data-table-pagin import { usePaginatedQueryParams, usePaginatedTable, + useSortQueryParams, } from "@/hooks/use-paginated-data-table"; import type { TGetManyStakeholderRes } from "@/server/api/client-handlers/stakeholder"; import { useManyStakeholder } from "@/server/api/client-hooks/stakeholder"; @@ -213,10 +214,8 @@ export const columns: ColumnDef[] = [ ]; const StakeholderTable = ({ companyId }: StakeholderTableType) => { - const [{ limit, page }, setData] = usePaginatedQueryParams(); - - const pageIndex = page - 1; - const pageSize = limit; + const { limit, page, onPaginationChange, pageIndex, pageSize } = + usePaginatedQueryParams(); const { data } = useManyStakeholder({ searchParams: { limit, page }, @@ -233,16 +232,7 @@ const StakeholderTable = ({ companyId }: StakeholderTableType) => { pageSize, }, }, - onPaginationChange: (updater) => { - if (typeof updater === "function") { - const updateValue = updater({ pageIndex, pageSize }); - - setData({ - limit: updateValue.pageSize, - page: updateValue.pageIndex + 1, - }); - } - }, + onPaginationChange, }); return (
diff --git a/src/hooks/use-paginated-data-table.ts b/src/hooks/use-paginated-data-table.ts index 312422dcc..d8d4d80a0 100644 --- a/src/hooks/use-paginated-data-table.ts +++ b/src/hooks/use-paginated-data-table.ts @@ -1,5 +1,10 @@ -import type { RowData, TableState } from "@tanstack/react-table"; -import { parseAsInteger, useQueryStates } from "nuqs"; +import type { + PaginationState, + RowData, + TableState, + Updater, +} from "@tanstack/react-table"; +import { parseAsInteger, useQueryState, useQueryStates } from "nuqs"; import { type TDataTableOptions, useDataTable } from "./use-data-table"; type MakeRequired = Omit & Required>; @@ -7,10 +12,30 @@ type MakeRequired = Omit & Required>; type TState = MakeRequired, "pagination">; export function usePaginatedQueryParams() { - return useQueryStates({ + const [{ limit, page }, setParams] = useQueryStates({ page: parseAsInteger.withDefault(1), limit: parseAsInteger.withDefault(2), }); + + const pageIndex = page - 1; + const pageSize = limit; + + const onPaginationChange = (updater: Updater) => { + if (typeof updater === "function") { + const updateValue = updater({ pageIndex, pageSize }); + + setParams({ + limit: updateValue.pageSize, + page: updateValue.pageIndex + 1, + }); + } + }; + + return { pageIndex, pageSize, onPaginationChange, limit, page, setParams }; +} + +export function useSortQueryParams() { + return useQueryState("sort"); } export function usePaginatedTable( From fd34967e559895fd45b13a60ac6f658004f97822 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Thu, 29 Aug 2024 17:10:37 +0530 Subject: [PATCH 15/34] feat: add sort --- src/server/api/routes/stakeholder/getMany.ts | 21 +++++++----- src/server/api/schema/pagination.ts | 34 +++++++++++++++++++- src/server/api/schema/stakeholder.ts | 15 +++++++++ 3 files changed, 61 insertions(+), 9 deletions(-) diff --git a/src/server/api/routes/stakeholder/getMany.ts b/src/server/api/routes/stakeholder/getMany.ts index e183f308c..421daaf26 100644 --- a/src/server/api/routes/stakeholder/getMany.ts +++ b/src/server/api/routes/stakeholder/getMany.ts @@ -1,9 +1,10 @@ import { z } from "@hono/zod-openapi"; +import { OffsetPaginationResponseSchema } from "../../schema/pagination"; import { - OffsetPaginationQuerySchema, - OffsetPaginationResponseSchema, -} from "../../schema/pagination"; -import { StakeholderSchema } from "../../schema/stakeholder"; + ManyStakeholderQuerySchema, + StakeholderSchema, + parseManyStakeholderSortParam, +} from "../../schema/stakeholder"; import { authMiddleware, withAuthApiV1 } from "../../utils/endpoint-creator"; const ResponseSchema = z.object({ @@ -33,7 +34,7 @@ export const getMany = withAuthApiV1 path: "/v1/{companyId}/stakeholders", middleware: [authMiddleware()], request: { - query: OffsetPaginationQuerySchema, + query: ManyStakeholderQuerySchema, params: ParamsSchema, }, responses: { @@ -50,13 +51,17 @@ export const getMany = withAuthApiV1 .handler(async (c) => { const { membership } = c.get("session"); const { db } = c.get("services"); - const query = c.req.valid("query"); + const { limit, page, sort } = c.req.valid("query"); const [data, meta] = await db.stakeholder - .paginate({ where: { companyId: membership.companyId } }) + .paginate({ + where: { companyId: membership.companyId }, + orderBy: parseManyStakeholderSortParam(sort), + }) .withPages({ includePageCount: true, - ...query, + limit, + page, }); const response: z.infer = { diff --git a/src/server/api/schema/pagination.ts b/src/server/api/schema/pagination.ts index 3dc4e3455..d5b6be567 100644 --- a/src/server/api/schema/pagination.ts +++ b/src/server/api/schema/pagination.ts @@ -1,6 +1,11 @@ import { z } from "@hono/zod-openapi"; import { DEFAULT_PAGINATION_LIMIT, OFFSET_MAXIMUM_LIMIT } from "../const"; +const sortDirections = ["asc", "desc"] as const; +type SortDirection = (typeof sortDirections)[number]; + +const SortDirectionSchema = z.enum(sortDirections); + export const OffsetPaginationQuerySchema = z.object({ limit: z.coerce .number() @@ -33,7 +38,8 @@ export const OffsetPaginationQuerySchema = z.object({ example: 1, default: 1, minimum: 1, - }), + }) + .openapi("Offset Pagination query"), }); export const PaginationQuerySchema = z.object({ @@ -136,3 +142,29 @@ export const OffsetPaginationResponseSchema = z description: "A schema representing the pagination details of an offset-based pagination system.", }); + +export function generateSortParam(sortFields: T) { + type SortField = T[number]; + type SortParam = `${SortField}.${SortDirection}`; + + const sortParams = sortFields.flatMap((field) => + sortDirections.map((direction) => `${field}.${direction}` as const), + ) as readonly SortParam[]; + + const parseSortParam = (sort: S) => { + type Field = S extends `${infer F}.${SortDirection}` ? F : never; + const [field, direction] = sort.split(".") as [Field, SortDirection]; + return { [field]: direction } as { [K in Field]: SortDirection }; + }; + return { + schema: z.enum(sortParams as [SortParam, ...SortParam[]]).openapi({ + description: "sort by", + param: { + name: "sort", + in: "query", + }, + }), + sortParams, + parseSortParam, + }; +} diff --git a/src/server/api/schema/stakeholder.ts b/src/server/api/schema/stakeholder.ts index 854ff7470..b38d74e0c 100644 --- a/src/server/api/schema/stakeholder.ts +++ b/src/server/api/schema/stakeholder.ts @@ -4,6 +4,8 @@ import { } from "@/prisma/enums"; import { z } from "@hono/zod-openapi"; +import { OffsetPaginationQuerySchema, generateSortParam } from "./pagination"; + const StakeholderTypeArray = Object.values(StakeholderTypeEnum) as [ StakeholderTypeEnum, ...StakeholderTypeEnum[], @@ -106,6 +108,19 @@ export const UpdateStakeholderSchema = StakeholderSchema.omit({ description: "Update a stakeholder by ID", }); +const sortFields = generateSortParam(["createdAt", "name"] as const); + +export const parseManyStakeholderSortParam = sortFields.parseSortParam; +export const ManyStakeholderSortParams = sortFields.sortParams; + +export const ManyStakeholderQuerySchema = z + .object({ + sort: sortFields.schema.optional().default("createdAt.desc"), + }) + .merge(OffsetPaginationQuerySchema); + export type TStakeholderSchema = z.infer; export type TCreateStakeholderSchema = z.infer; export type TUpdateStakeholderSchema = z.infer; +export type TManyStakeholderSortParams = + (typeof ManyStakeholderSortParams)[number]; From e7e744988d266a0ae287e0348140850b59b7e1f7 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Thu, 29 Aug 2024 20:07:26 +0530 Subject: [PATCH 16/34] feat: add sort --- .../stakeholder/stakeholder-table.tsx | 133 +++++++----------- .../ui/data-table/data-table-header.tsx | 46 +++++- src/hooks/use-data-table.tsx | 2 +- src/hooks/use-paginated-data-table.ts | 57 +++++++- src/server/api/client-hooks/stakeholder.ts | 1 + src/server/api/schema/pagination.ts | 2 - src/server/api/schema/stakeholder.ts | 2 +- 7 files changed, 142 insertions(+), 101 deletions(-) diff --git a/src/components/stakeholder/stakeholder-table.tsx b/src/components/stakeholder/stakeholder-table.tsx index 644223636..eb8b40f36 100644 --- a/src/components/stakeholder/stakeholder-table.tsx +++ b/src/components/stakeholder/stakeholder-table.tsx @@ -5,7 +5,6 @@ import { Badge } from "@/components/ui/badge"; import { Checkbox } from "@/components/ui/checkbox"; import { DataTable } from "@/components/ui/data-table/data-table"; import { DataTableBody } from "@/components/ui/data-table/data-table-body"; -import { SortButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableContent } from "@/components/ui/data-table/data-table-content"; import { DataTableHeader } from "@/components/ui/data-table/data-table-header"; import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination"; @@ -16,10 +15,11 @@ import { } from "@/hooks/use-paginated-data-table"; import type { TGetManyStakeholderRes } from "@/server/api/client-handlers/stakeholder"; import { useManyStakeholder } from "@/server/api/client-hooks/stakeholder"; +import { ManyStakeholderSortParams } from "@/server/api/schema/stakeholder"; import type { RouterOutputs } from "@/trpc/shared"; import { RiMore2Fill } from "@remixicon/react"; -import type { ColumnDef, PaginationState } from "@tanstack/react-table"; -import { use, useState } from "react"; +import { createColumnHelper } from "@tanstack/react-table"; + import { Allow } from "../rbac/allow"; import { Button } from "../ui/button"; import { @@ -79,8 +79,11 @@ const getCurrentRelationship = (relationship: string) => { } }; -export const columns: ColumnDef[] = [ - { +const columnHelper = + createColumnHelper(); + +const columns = [ + columnHelper.display({ id: "select", header: ({ table }) => ( [] = [ aria-label="Select row" /> ), - enableSorting: false, enableHiding: false, - }, - { - id: "name", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - accessorFn: (row) => row.name, - cell: ({ row }) => ( -
{row.getValue("name")}
- ), - }, - { - accessorKey: "email", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) =>
{row.getValue("email")}
, - }, - { - accessorKey: "Institution name", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) =>
{row.original.institutionName ?? ""}
, - }, - { - accessorKey: "Type", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => { - const type = row.original.stakeholderType as string; + enableSorting: false, + }), + columnHelper.accessor("name", { + header: "Name", + cell: (row) =>
{row.getValue()}
, + }), + columnHelper.accessor("email", { + header: "Email", + cell: (info) => info.getValue(), + }), + columnHelper.accessor("institutionName", { + header: "Institute name", + cell: (info) => info.getValue(), + enableSorting: false, + }), + columnHelper.accessor("stakeholderType", { + header: "Type", + cell: (info) => { + const type = info.getValue(); return ( {getStakeholderType(type)} ); }, - }, - { - accessorKey: "Association", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => { - return ( -
{getCurrentRelationship(row.original.currentRelationship)}
- ); - }, - }, - { - id: "actions", + enableSorting: false, + }), + columnHelper.accessor("currentRelationship", { + header: "Association", + cell: (info) => getCurrentRelationship(info.getValue()), + enableSorting: false, + }), + + columnHelper.display({ + id: "Actions", enableHiding: false, cell: ({ row }) => { const singleStakeholder = row.original; @@ -210,15 +170,20 @@ export const columns: ColumnDef[] = [ ); }, - }, + }), ]; const StakeholderTable = ({ companyId }: StakeholderTableType) => { - const { limit, page, onPaginationChange, pageIndex, pageSize } = + const { limit, page, onPaginationChange, pagination } = usePaginatedQueryParams(); + const { onSortingChange, sorting, sort } = useSortQueryParams( + ManyStakeholderSortParams, + "createdAt.desc", + ); + const { data } = useManyStakeholder({ - searchParams: { limit, page }, + searchParams: { limit, page, sort }, urlParams: { companyId }, }); @@ -227,12 +192,12 @@ const StakeholderTable = ({ companyId }: StakeholderTableType) => { columns, data: data?.data ?? [], state: { - pagination: { - pageIndex, - pageSize, - }, + pagination, + sorting, }, onPaginationChange, + onSortingChange, + manualSorting: true, }); return (
diff --git a/src/components/ui/data-table/data-table-header.tsx b/src/components/ui/data-table/data-table-header.tsx index 5e37eaaa9..5d66d3d1c 100644 --- a/src/components/ui/data-table/data-table-header.tsx +++ b/src/components/ui/data-table/data-table-header.tsx @@ -1,8 +1,45 @@ -import { flexRender } from "@tanstack/react-table"; +import { type Header, flexRender } from "@tanstack/react-table"; import { TableHead, TableHeader, TableRow } from "../table"; +import { + RiArrowDownSLine, + RiArrowUpSLine, + RiExpandUpDownLine, +} from "@remixicon/react"; import { useTable } from "./data-table"; +// biome-ignore lint/suspicious/noExplicitAny: +function HeaderItem({ header }: { header: Header }) { + const canSort = header.column.getCanSort(); + const Element = canSort ? "button" : "div"; + + const label = canSort + ? header.column.getNextSortingOrder() === "asc" + ? "Sort ascending" + : header.column.getNextSortingOrder() === "desc" + ? "Sort descending" + : "Clear sort" + : undefined; + return ( + + {flexRender(header.column.columnDef.header, header.getContext())} + {{ + asc: , + desc: , + }[header.column.getIsSorted() as string] ?? null} + + {canSort && !header.column.getIsSorted() ? ( + + ) : null} + + ); +} + export function DataTableHeader() { const { table } = useTable(); return ( @@ -12,12 +49,7 @@ export function DataTableHeader() { {headerGroup.headers.map((header) => { return ( - {header.isPlaceholder - ? null - : flexRender( - header.column.columnDef.header, - header.getContext(), - )} + {header.isPlaceholder ? null : } ); })} diff --git a/src/hooks/use-data-table.tsx b/src/hooks/use-data-table.tsx index 82b458167..6dcb2df9a 100644 --- a/src/hooks/use-data-table.tsx +++ b/src/hooks/use-data-table.tsx @@ -30,7 +30,6 @@ export function useDataTable( const [rowSelection, setRowSelection] = useState({}); return useReactTable({ - ...options, enableRowSelection: true, onRowSelectionChange: setRowSelection, onSortingChange: setSorting, @@ -49,5 +48,6 @@ export function useDataTable( rowSelection, ...(options?.state && { ...options.state }), }, + ...options, }); } diff --git a/src/hooks/use-paginated-data-table.ts b/src/hooks/use-paginated-data-table.ts index d8d4d80a0..8ea7d1730 100644 --- a/src/hooks/use-paginated-data-table.ts +++ b/src/hooks/use-paginated-data-table.ts @@ -1,10 +1,16 @@ import type { PaginationState, RowData, + SortingState, TableState, Updater, } from "@tanstack/react-table"; -import { parseAsInteger, useQueryState, useQueryStates } from "nuqs"; +import { + parseAsInteger, + parseAsStringLiteral, + useQueryState, + useQueryStates, +} from "nuqs"; import { type TDataTableOptions, useDataTable } from "./use-data-table"; type MakeRequired = Omit & Required>; @@ -14,7 +20,7 @@ type TState = MakeRequired, "pagination">; export function usePaginatedQueryParams() { const [{ limit, page }, setParams] = useQueryStates({ page: parseAsInteger.withDefault(1), - limit: parseAsInteger.withDefault(2), + limit: parseAsInteger.withDefault(10), }); const pageIndex = page - 1; @@ -31,21 +37,60 @@ export function usePaginatedQueryParams() { } }; - return { pageIndex, pageSize, onPaginationChange, limit, page, setParams }; + return { + pagination: { pageIndex, pageSize }, + onPaginationChange, + limit, + page, + setParams, + }; +} + +function parseSortingState(sort: string) { + const [field, direction] = sort.split("."); + + const state: SortingState = [{ id: field ?? "", desc: direction === "desc" }]; + + return state; } -export function useSortQueryParams() { - return useQueryState("sort"); +export function useSortQueryParams< + T extends readonly string[], + U extends T[number], + V extends T[number], +>(schema: T, defaultValue: U) { + const [sort, setSort] = useQueryState( + "sort", + //@ts-expect-error + parseAsStringLiteral(schema).withDefault(defaultValue), + ); + + const sorting = parseSortingState(sort); + + const onSortingChange = (updater: Updater) => { + if (typeof updater === "function") { + const updateValue = updater(sorting); + + const sortValue = updateValue[0] + ? `${updateValue[0]?.id}.${updateValue[0]?.desc ? "desc" : "asc"}` + : defaultValue; + + setSort(sortValue as V); + } + }; + + return { onSortingChange, setSort, sort, sorting }; } export function usePaginatedTable( options: Omit, "state"> & { state: TState }, ) { return useDataTable({ - ...options, manualPagination: true, manualSorting: true, manualFiltering: true, pageCount: options.pageCount ?? -1, + + ...options, }); } diff --git a/src/server/api/client-hooks/stakeholder.ts b/src/server/api/client-hooks/stakeholder.ts index 7f8605d9a..765c3f446 100644 --- a/src/server/api/client-hooks/stakeholder.ts +++ b/src/server/api/client-hooks/stakeholder.ts @@ -13,6 +13,7 @@ export const useManyStakeholder = (data: TGetManyStakeholderParams) => data.urlParams.companyId, String(data.searchParams.limit), String(data.searchParams.page), + String(data.searchParams.sort), ], queryFn: () => getManyStakeholder(data), }); diff --git a/src/server/api/schema/pagination.ts b/src/server/api/schema/pagination.ts index d5b6be567..8b580bbe3 100644 --- a/src/server/api/schema/pagination.ts +++ b/src/server/api/schema/pagination.ts @@ -4,8 +4,6 @@ import { DEFAULT_PAGINATION_LIMIT, OFFSET_MAXIMUM_LIMIT } from "../const"; const sortDirections = ["asc", "desc"] as const; type SortDirection = (typeof sortDirections)[number]; -const SortDirectionSchema = z.enum(sortDirections); - export const OffsetPaginationQuerySchema = z.object({ limit: z.coerce .number() diff --git a/src/server/api/schema/stakeholder.ts b/src/server/api/schema/stakeholder.ts index b38d74e0c..75602a8fa 100644 --- a/src/server/api/schema/stakeholder.ts +++ b/src/server/api/schema/stakeholder.ts @@ -108,7 +108,7 @@ export const UpdateStakeholderSchema = StakeholderSchema.omit({ description: "Update a stakeholder by ID", }); -const sortFields = generateSortParam(["createdAt", "name"] as const); +const sortFields = generateSortParam(["createdAt", "name", "email"] as const); export const parseManyStakeholderSortParam = sortFields.parseSortParam; export const ManyStakeholderSortParams = sortFields.sortParams; From 85116f3bf9349338420cb4ddd58903c37ccb7cdb Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Thu, 29 Aug 2024 20:09:55 +0530 Subject: [PATCH 17/34] chore: hide select --- .../stakeholder/stakeholder-table.tsx | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/src/components/stakeholder/stakeholder-table.tsx b/src/components/stakeholder/stakeholder-table.tsx index eb8b40f36..e0e08294b 100644 --- a/src/components/stakeholder/stakeholder-table.tsx +++ b/src/components/stakeholder/stakeholder-table.tsx @@ -83,28 +83,28 @@ const columnHelper = createColumnHelper(); const columns = [ - columnHelper.display({ - id: "select", - header: ({ table }) => ( - table.toggleAllPageRowsSelected(!!value)} - aria-label="Select all" - /> - ), - cell: ({ row }) => ( - row.toggleSelected(!!value)} - aria-label="Select row" - /> - ), - enableHiding: false, - enableSorting: false, - }), + // columnHelper.display({ + // id: "select", + // header: ({ table }) => ( + // table.toggleAllPageRowsSelected(!!value)} + // aria-label="Select all" + // /> + // ), + // cell: ({ row }) => ( + // row.toggleSelected(!!value)} + // aria-label="Select row" + // /> + // ), + // enableHiding: false, + // enableSorting: false, + // }), columnHelper.accessor("name", { header: "Name", cell: (row) =>
{row.getValue()}
, From 4ddcf2a240aadac1865374beccae86828d74b137 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Thu, 29 Aug 2024 21:57:15 +0530 Subject: [PATCH 18/34] feat: add search filter --- .../stakeholder/stakeholder-table.tsx | 9 ++- src/hooks/use-paginated-data-table.ts | 67 ++++++++++++++++++- 2 files changed, 74 insertions(+), 2 deletions(-) diff --git a/src/components/stakeholder/stakeholder-table.tsx b/src/components/stakeholder/stakeholder-table.tsx index e0e08294b..31c7d2bec 100644 --- a/src/components/stakeholder/stakeholder-table.tsx +++ b/src/components/stakeholder/stakeholder-table.tsx @@ -9,6 +9,7 @@ import { DataTableContent } from "@/components/ui/data-table/data-table-content" import { DataTableHeader } from "@/components/ui/data-table/data-table-header"; import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination"; import { + useFilterQueryParams, usePaginatedQueryParams, usePaginatedTable, useSortQueryParams, @@ -20,6 +21,7 @@ import type { RouterOutputs } from "@/trpc/shared"; import { RiMore2Fill } from "@remixicon/react"; import { createColumnHelper } from "@tanstack/react-table"; +import { parseAsString } from "nuqs"; import { Allow } from "../rbac/allow"; import { Button } from "../ui/button"; import { @@ -181,6 +183,9 @@ const StakeholderTable = ({ companyId }: StakeholderTableType) => { ManyStakeholderSortParams, "createdAt.desc", ); + const { columnFilters, onColumnFiltersChange } = useFilterQueryParams({ + name: parseAsString, + }); const { data } = useManyStakeholder({ searchParams: { limit, page, sort }, @@ -194,11 +199,13 @@ const StakeholderTable = ({ companyId }: StakeholderTableType) => { state: { pagination, sorting, + columnFilters, }, onPaginationChange, onSortingChange, - manualSorting: true, + onColumnFiltersChange, }); + return (
diff --git a/src/hooks/use-paginated-data-table.ts b/src/hooks/use-paginated-data-table.ts index 8ea7d1730..70e3745f2 100644 --- a/src/hooks/use-paginated-data-table.ts +++ b/src/hooks/use-paginated-data-table.ts @@ -1,4 +1,6 @@ import type { + ColumnFilter, + ColumnFiltersState, PaginationState, RowData, SortingState, @@ -6,11 +8,14 @@ import type { Updater, } from "@tanstack/react-table"; import { + type UseQueryStatesKeysMap, + type Values, parseAsInteger, parseAsStringLiteral, useQueryState, useQueryStates, } from "nuqs"; +import { useMemo } from "react"; import { type TDataTableOptions, useDataTable } from "./use-data-table"; type MakeRequired = Omit & Required>; @@ -82,6 +87,67 @@ export function useSortQueryParams< return { onSortingChange, setSort, sort, sorting }; } +function parseFilteringState( + filter: Values, +) { + const columnFilters: ColumnFilter[] = []; + + for (const key in filter) { + if (Object.prototype.hasOwnProperty.call(filter, key)) { + const value = filter[key]; + if (value) { + columnFilters.push({ id: key, value }); + } + } + } + + return columnFilters; +} + +export function useFilterQueryParams( + keyMap: KeyMap, +) { + const [state, setState] = useQueryStates(keyMap); + + const columnFilters = parseFilteringState(state); + + // biome-ignore lint/correctness/useExhaustiveDependencies: + const defaultValue = useMemo(() => { + const keys = Object.keys(keyMap); + + // biome-ignore lint/suspicious/noExplicitAny: + return keys.reduce>((prev, curr) => { + const defaultValue = keyMap[curr as keyof KeyMap]?.defaultValue ?? null; + + prev[curr] = defaultValue; + return prev; + }, {}); + }, []); + + const onColumnFiltersChange = (updater: Updater) => { + if (typeof updater === "function") { + const updateValue = updater(columnFilters); + + if (updateValue.length) { + const stateValue = updateValue.reduce>( + (prev, current) => { + prev[current.id] = current.value; + return prev; + }, + {}, + ); + + // biome-ignore lint/suspicious/noExplicitAny: + setState(stateValue as any); + } else { + // biome-ignore lint/suspicious/noExplicitAny: + setState(defaultValue as any); + } + } + }; + return { columnFilters, onColumnFiltersChange }; +} + export function usePaginatedTable( options: Omit, "state"> & { state: TState }, ) { @@ -90,7 +156,6 @@ export function usePaginatedTable( manualSorting: true, manualFiltering: true, pageCount: options.pageCount ?? -1, - ...options, }); } From 9851bf3dad2c1034d1ceca10f5cf2d34d4c1a31b Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Thu, 29 Aug 2024 22:14:00 +0530 Subject: [PATCH 19/34] feat: refactor data --- src/hooks/use-paginated-data-table.ts | 46 +++++++++++++++------------ 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/src/hooks/use-paginated-data-table.ts b/src/hooks/use-paginated-data-table.ts index 70e3745f2..679ec7428 100644 --- a/src/hooks/use-paginated-data-table.ts +++ b/src/hooks/use-paginated-data-table.ts @@ -104,6 +104,28 @@ function parseFilteringState( return columnFilters; } +function keyMapInitial(keyMap: KeyMap) { + return Object.keys(keyMap).reduce( + (obj, key) => { + // biome-ignore lint/style/noNonNullAssertion: + const { defaultValue } = keyMap[key]!; + + obj[key as keyof KeyMap] = defaultValue ?? null; + return obj; + }, + {} as Values, + ); +} + +function toQueryState( + updateValue: ColumnFiltersState, +) { + return updateValue.reduce>((prev, current) => { + prev[current.id] = current.value; + return prev; + }, {}) as Values; +} + export function useFilterQueryParams( keyMap: KeyMap, ) { @@ -113,15 +135,7 @@ export function useFilterQueryParams( // biome-ignore lint/correctness/useExhaustiveDependencies: const defaultValue = useMemo(() => { - const keys = Object.keys(keyMap); - - // biome-ignore lint/suspicious/noExplicitAny: - return keys.reduce>((prev, curr) => { - const defaultValue = keyMap[curr as keyof KeyMap]?.defaultValue ?? null; - - prev[curr] = defaultValue; - return prev; - }, {}); + return keyMapInitial(keyMap); }, []); const onColumnFiltersChange = (updater: Updater) => { @@ -129,19 +143,9 @@ export function useFilterQueryParams( const updateValue = updater(columnFilters); if (updateValue.length) { - const stateValue = updateValue.reduce>( - (prev, current) => { - prev[current.id] = current.value; - return prev; - }, - {}, - ); - - // biome-ignore lint/suspicious/noExplicitAny: - setState(stateValue as any); + setState(toQueryState(updateValue)); } else { - // biome-ignore lint/suspicious/noExplicitAny: - setState(defaultValue as any); + setState(defaultValue); } } }; From 69a423ea5dff522298902c88ea0b59e89649e43f Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Thu, 29 Aug 2024 22:33:55 +0530 Subject: [PATCH 20/34] feat: add debounce component --- src/components/ui/debounced-input.tsx | 36 +++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 src/components/ui/debounced-input.tsx diff --git a/src/components/ui/debounced-input.tsx b/src/components/ui/debounced-input.tsx new file mode 100644 index 000000000..620cfd72c --- /dev/null +++ b/src/components/ui/debounced-input.tsx @@ -0,0 +1,36 @@ +import { useEffect, useState } from "react"; +import { Input, type InputProps } from "./input"; + +export function DebouncedInput({ + value: initialValue, + onChange, + debounce = 500, + ...props +}: { + value: string | number; + onChange: (value: string | number) => void; + debounce?: number; +} & Omit) { + const [value, setValue] = useState(initialValue); + + useEffect(() => { + setValue(initialValue); + }, [initialValue]); + + // biome-ignore lint/correctness/useExhaustiveDependencies: + useEffect(() => { + const timeout = setTimeout(() => { + onChange(value); + }, debounce); + + return () => clearTimeout(timeout); + }, [value]); + + return ( + setValue(e.target.value)} + /> + ); +} From 2409c0d6381b93465bb057047cbe9ccc8a262cf4 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Thu, 29 Aug 2024 22:34:10 +0530 Subject: [PATCH 21/34] feat: add debounce input --- .../stakeholder/stakeholder-table-toolbar.tsx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/components/stakeholder/stakeholder-table-toolbar.tsx b/src/components/stakeholder/stakeholder-table-toolbar.tsx index cf1d66074..e2e5c48a8 100644 --- a/src/components/stakeholder/stakeholder-table-toolbar.tsx +++ b/src/components/stakeholder/stakeholder-table-toolbar.tsx @@ -1,21 +1,20 @@ import { useTable } from "@/components/ui/data-table/data-table"; import { ResetButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableViewOptions } from "@/components/ui/data-table/data-table-view-options"; -import { Input } from "@/components/ui/input"; +import { DebouncedInput } from "../ui/debounced-input"; export const StakeholderTableToolbar = () => { const { table } = useTable(); const isFiltered = table.getState().columnFilters.length > 0; + const value = table.getColumn("name")?.getFilterValue() as string; return (
- - table.getColumn("name")?.setFilterValue(event.target.value) - } + value={value} + onChange={(value) => table.getColumn("name")?.setFilterValue(value)} className="h-8 w-64" />
From 352391136c50ee2bc7db276caf064543293e3082 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Fri, 30 Aug 2024 06:57:34 +0530 Subject: [PATCH 22/34] feat: add search --- src/components/stakeholder/stakeholder-table.tsx | 9 +++++++-- src/hooks/use-paginated-data-table.ts | 2 +- src/server/api/client-hooks/stakeholder.ts | 1 + src/server/api/routes/stakeholder/getMany.ts | 7 +++++-- src/server/api/schema/stakeholder.ts | 1 + 5 files changed, 15 insertions(+), 5 deletions(-) diff --git a/src/components/stakeholder/stakeholder-table.tsx b/src/components/stakeholder/stakeholder-table.tsx index 31c7d2bec..a6d85822b 100644 --- a/src/components/stakeholder/stakeholder-table.tsx +++ b/src/components/stakeholder/stakeholder-table.tsx @@ -183,12 +183,17 @@ const StakeholderTable = ({ companyId }: StakeholderTableType) => { ManyStakeholderSortParams, "createdAt.desc", ); - const { columnFilters, onColumnFiltersChange } = useFilterQueryParams({ + const { columnFilters, onColumnFiltersChange, state } = useFilterQueryParams({ name: parseAsString, }); const { data } = useManyStakeholder({ - searchParams: { limit, page, sort }, + searchParams: { + limit, + page, + sort, + ...(state.name && { name: state.name }), + }, urlParams: { companyId }, }); diff --git a/src/hooks/use-paginated-data-table.ts b/src/hooks/use-paginated-data-table.ts index 679ec7428..6e90af657 100644 --- a/src/hooks/use-paginated-data-table.ts +++ b/src/hooks/use-paginated-data-table.ts @@ -149,7 +149,7 @@ export function useFilterQueryParams( } } }; - return { columnFilters, onColumnFiltersChange }; + return { columnFilters, onColumnFiltersChange, state }; } export function usePaginatedTable( diff --git a/src/server/api/client-hooks/stakeholder.ts b/src/server/api/client-hooks/stakeholder.ts index 765c3f446..e29de104b 100644 --- a/src/server/api/client-hooks/stakeholder.ts +++ b/src/server/api/client-hooks/stakeholder.ts @@ -14,6 +14,7 @@ export const useManyStakeholder = (data: TGetManyStakeholderParams) => String(data.searchParams.limit), String(data.searchParams.page), String(data.searchParams.sort), + String(data.searchParams?.name ?? ""), ], queryFn: () => getManyStakeholder(data), }); diff --git a/src/server/api/routes/stakeholder/getMany.ts b/src/server/api/routes/stakeholder/getMany.ts index 421daaf26..3f6865d77 100644 --- a/src/server/api/routes/stakeholder/getMany.ts +++ b/src/server/api/routes/stakeholder/getMany.ts @@ -51,11 +51,14 @@ export const getMany = withAuthApiV1 .handler(async (c) => { const { membership } = c.get("session"); const { db } = c.get("services"); - const { limit, page, sort } = c.req.valid("query"); + const { limit, page, sort, name } = c.req.valid("query"); const [data, meta] = await db.stakeholder .paginate({ - where: { companyId: membership.companyId }, + where: { + companyId: membership.companyId, + ...(name && name !== "" && { name: { contains: "" } }), + }, orderBy: parseManyStakeholderSortParam(sort), }) .withPages({ diff --git a/src/server/api/schema/stakeholder.ts b/src/server/api/schema/stakeholder.ts index 75602a8fa..f01aa8d30 100644 --- a/src/server/api/schema/stakeholder.ts +++ b/src/server/api/schema/stakeholder.ts @@ -116,6 +116,7 @@ export const ManyStakeholderSortParams = sortFields.sortParams; export const ManyStakeholderQuerySchema = z .object({ sort: sortFields.schema.optional().default("createdAt.desc"), + name: z.string().optional(), }) .merge(OffsetPaginationQuerySchema); From 8810540783cd1c23f6b8e761e9c1b9760d4b4768 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Fri, 30 Aug 2024 08:18:52 +0530 Subject: [PATCH 23/34] fix: query --- src/server/api/routes/stakeholder/getMany.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/server/api/routes/stakeholder/getMany.ts b/src/server/api/routes/stakeholder/getMany.ts index 3f6865d77..6ef74990b 100644 --- a/src/server/api/routes/stakeholder/getMany.ts +++ b/src/server/api/routes/stakeholder/getMany.ts @@ -57,7 +57,7 @@ export const getMany = withAuthApiV1 .paginate({ where: { companyId: membership.companyId, - ...(name && name !== "" && { name: { contains: "" } }), + ...(name && { name: { contains: name, mode: "insensitive" } }), }, orderBy: parseManyStakeholderSortParam(sort), }) From 5acdd8111314ec4bf7e8832ecb97db2abf2d38c5 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Fri, 30 Aug 2024 18:58:54 +0530 Subject: [PATCH 24/34] feat: update tables --- src/components/audit/audit-table/index.tsx | 66 +++--- src/components/member/member-table.tsx | 115 ++++------ src/components/rbac/role-table.tsx | 47 ++-- src/components/safe/safe-table/index.tsx | 163 +++++--------- .../securities/options/option-table.tsx | 157 ++++--------- .../securities/shares/share-table.tsx | 212 ++++++------------ .../passkey/user-passkeys-data-table.tsx | 111 +++------ .../ui/data-table/data-table-buttons.tsx | 16 +- src/components/update/update-table.tsx | 72 +++--- 9 files changed, 314 insertions(+), 645 deletions(-) diff --git a/src/components/audit/audit-table/index.tsx b/src/components/audit/audit-table/index.tsx index 38ce799c9..599dfd12a 100644 --- a/src/components/audit/audit-table/index.tsx +++ b/src/components/audit/audit-table/index.tsx @@ -5,13 +5,12 @@ import { Badge } from "@/components/ui/badge"; import { Checkbox } from "@/components/ui/checkbox"; import { DataTable } from "@/components/ui/data-table/data-table"; import { DataTableBody } from "@/components/ui/data-table/data-table-body"; -import { SortButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableContent } from "@/components/ui/data-table/data-table-content"; import { DataTableHeader } from "@/components/ui/data-table/data-table-header"; import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination"; import { useDataTable } from "@/hooks/use-data-table"; import type { RouterOutputs } from "@/trpc/shared"; -import type { ColumnDef } from "@tanstack/react-table"; +import { type ColumnDef, createColumnHelper } from "@tanstack/react-table"; import * as React from "react"; import { AuditTableToolbar } from "./audit-table-toolbar"; @@ -21,8 +20,10 @@ interface AuditTableProps { audits: Audit; } -export const columns: ColumnDef[] = [ - { +const columnHelper = createColumnHelper(); + +export const columns = [ + columnHelper.display({ id: "select", header: ({ table }) => ( [] = [ ), enableSorting: false, enableHiding: false, - }, - { - id: "action", - accessorKey: "action", - header: () => { - return
Action
; - }, - cell: ({ row }) => ( + }), + + columnHelper.accessor("action", { + header: "Action", + cell: (row) => (
- {row.getValue("action")} + {row.getValue()}
), - filterFn: (row, id, value: string[]) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return + filterFn: (row, id, value) => { return value.includes(row.getValue(id)); }, - }, + }), - { - id: "occurredAt", - accessorKey: "occurredAt", - header: ({ column }) => { + columnHelper.accessor("occurredAt", { + header: "Time", + cell: (row) => { + const date = new Date(row.getValue()); + const formattedDate = dayjsExt(date).format("lll"); return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> + ); }, - cell: ({ row }) => { - const date = new Date(row.getValue("occurredAt")); - const formattedDate = dayjsExt(date).format("lll"); - return ; - }, - }, + }), - { - id: "summary", - accessorKey: "summary", - header: () => { - return
Summary
; - }, - cell: ({ row }) => { - return

{row.getValue("summary")}

; - }, - }, + columnHelper.accessor("summary", { + header: "Summary", + cell: (row) => row.getValue(), + }), ]; export function AuditTable({ audits }: AuditTableProps) { diff --git a/src/components/member/member-table.tsx b/src/components/member/member-table.tsx index 885094967..05f7e408d 100644 --- a/src/components/member/member-table.tsx +++ b/src/components/member/member-table.tsx @@ -1,8 +1,8 @@ "use client"; -import * as React from "react"; +import type * as React from "react"; -import type { ColumnDef } from "@tanstack/react-table"; +import { type ColumnDef, createColumnHelper } from "@tanstack/react-table"; import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; @@ -21,6 +21,7 @@ import { Avatar, AvatarImage } from "@/components/ui/avatar"; import { useDataTable } from "@/hooks/use-data-table"; import { getRoleId } from "@/lib/rbac/access-control-utils"; +import type { MemberStatusEnum } from "@/prisma/enums"; import type { RouterOutputs } from "@/trpc/shared"; import { RiMore2Fill } from "@remixicon/react"; import { useSession } from "next-auth/react"; @@ -28,7 +29,6 @@ import { useRouter } from "next/navigation"; import { pushModal } from "../modals"; import { DataTable } from "../ui/data-table/data-table"; import { DataTableBody } from "../ui/data-table/data-table-body"; -import { SortButton } from "../ui/data-table/data-table-buttons"; import { DataTableContent } from "../ui/data-table/data-table-content"; import { DataTableHeader } from "../ui/data-table/data-table-header"; import { DataTablePagination } from "../ui/data-table/data-table-pagination"; @@ -42,33 +42,28 @@ type MembersType = { roles: Roles; }; -const humanizeStatus = (status: string) => { - if (status === "PENDING") { - return ( - - Pending - - ); - } - if (status === "ACTIVE") { - return ( - - Active - - ); - } - if (status === "INACTIVE") { - return ( - - Inactive - - ); - } - return "Unknown"; +const humanizeStatus: Record = { + ACTIVE: ( + + Active + + ), + INACTIVE: ( + + Inactive + + ), + PENDING: ( + + Pending + + ), }; -export const columns: ColumnDef[] = [ - { +const columnHelper = createColumnHelper(); + +export const columns = [ + columnHelper.display({ id: "select", header: ({ table }) => ( [] = [ ), enableSorting: false, enableHiding: false, - }, - { - id: "name", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - accessorFn: (row) => row.user?.name, - cell: ({ row }) => ( + }), + columnHelper.accessor("user.name", { + header: "Name", + cell: (row) => (
-

{row.original?.user?.name}

-

{row.original?.user?.email}

+

{row.getValue()}

+

{row.row.original?.user?.email}

), - }, - { - accessorKey: "title", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) =>
{row.getValue("title")}
, - }, - { - accessorKey: "status", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) =>
{humanizeStatus(row.original.status)}
, + }), + columnHelper.accessor("title", { + header: "Title", + cell: (row) =>
{row.getValue()}
, + }), + columnHelper.accessor("status", { + header: "Status", + cell: (row) =>
{humanizeStatus[row.getValue()]}
, filterFn: (row, id, value: string[]) => { - // eslint-disable-next-line @typescript-eslint/no-unsafe-return return value.includes(row.getValue(id)); }, - }, - { + }), + + columnHelper.display({ id: "actions", enableHiding: false, + enableSorting: false, cell: ({ row, table }) => { // eslint-disable-next-line react-hooks/rules-of-hooks const router = useRouter(); @@ -267,7 +238,7 @@ export const columns: ColumnDef[] = [ ); }, - }, + }), ]; const MemberTable = ({ members, roles }: MembersType) => { diff --git a/src/components/rbac/role-table.tsx b/src/components/rbac/role-table.tsx index 216348bd7..608e2adf7 100644 --- a/src/components/rbac/role-table.tsx +++ b/src/components/rbac/role-table.tsx @@ -6,7 +6,7 @@ import type { TPermission } from "@/lib/rbac/schema"; import { api } from "@/trpc/react"; import type { RouterOutputs } from "@/trpc/shared"; import { RiMore2Fill } from "@remixicon/react"; -import type { ColumnDef } from "@tanstack/react-table"; +import { type ColumnDef, createColumnHelper } from "@tanstack/react-table"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { pushModal } from "../modals"; @@ -16,7 +16,6 @@ import { Button } from "../ui/button"; import { Checkbox } from "../ui/checkbox"; import { DataTable } from "../ui/data-table/data-table"; import { DataTableBody } from "../ui/data-table/data-table-body"; -import { SortButton } from "../ui/data-table/data-table-buttons"; import { DataTableContent } from "../ui/data-table/data-table-content"; import { DataTableHeader } from "../ui/data-table/data-table-header"; import { DataTablePagination } from "../ui/data-table/data-table-pagination"; @@ -36,8 +35,10 @@ interface RoleTableProps { roles: Role[]; } -export const columns: ColumnDef[] = [ - { +const columnHelper = createColumnHelper(); + +export const columns = [ + columnHelper.display({ id: "select", header: ({ table }) => ( [] = [ ), enableSorting: false, enableHiding: false, - }, - { - id: "name", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - accessorKey: "name", - cell: ({ row }) => { - return
{row.getValue("name")}
; - }, - }, - { - accessorKey: "type", + }), + + columnHelper.accessor("name", { + header: "Name", + cell: (row) => row.getValue(), + }), + + columnHelper.accessor("type", { header: () =>
Type
, - cell: ({ row }) => { - const type = row.original.type; + cell: (row) => { + const type = row.getValue(); return (
@@ -87,9 +78,11 @@ export const columns: ColumnDef[] = [
); }, - }, - { + }), + + columnHelper.display({ id: "actions", + enableSorting: false, enableHiding: false, cell: ({ row }) => { const role = row.original; @@ -166,7 +159,7 @@ export const columns: ColumnDef[] = [ ); }, - }, + }), ]; function getPermission(permissions_: TPermission[]) { diff --git a/src/components/safe/safe-table/index.tsx b/src/components/safe/safe-table/index.tsx index d4dfbb31c..17e5d13d2 100644 --- a/src/components/safe/safe-table/index.tsx +++ b/src/components/safe/safe-table/index.tsx @@ -1,6 +1,6 @@ "use client"; -import type { ColumnDef } from "@tanstack/react-table"; +import { createColumnHelper } from "@tanstack/react-table"; import * as React from "react"; import { Button } from "@/components/ui/button"; @@ -19,7 +19,6 @@ import { api } from "@/trpc/react"; import { Avatar, AvatarImage } from "@/components/ui/avatar"; import { DataTable } from "@/components/ui/data-table/data-table"; import { DataTableBody } from "@/components/ui/data-table/data-table-body"; -import { SortButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableContent } from "@/components/ui/data-table/data-table-content"; import { DataTableHeader } from "@/components/ui/data-table/data-table-header"; import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination"; @@ -37,8 +36,10 @@ type SafesType = { safes: Safe; }; -export const columns: ColumnDef[] = [ - { +const columnHelper = createColumnHelper(); + +export const columns = [ + columnHelper.display({ id: "select", header: ({ table }) => ( [] = [ ), enableSorting: false, enableHiding: false, - }, - { - id: "stakeholderName", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - accessorFn: (row) => row?.stakeholder?.name, - cell: ({ row }) => ( + }), + + columnHelper.accessor("stakeholder.name", { + header: "Stakeholder", + cell: (row) => (
-

{row?.original?.stakeholder?.name}

+

{row.getValue()}

), - }, - { - accessorKey: "capital", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => ( -
{row.getValue("capital")}
- ), - }, - { - accessorKey: "valuationCap", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => ( -
{row.getValue("valuationCap")}
- ), - }, - { - accessorKey: "type", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) =>
{row.getValue("type")}
, - }, - { - accessorKey: "status", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => ( -
{row.getValue("status")}
- ), - }, - { - accessorKey: "discountRate", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => ( -
{row.getValue("discountRate")}
- ), - }, - { - accessorKey: "issueDate", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => ( -
- {new Date().toLocaleDateString(row.getValue("issueDate"))} + }), + + columnHelper.accessor("capital", { + header: "Capital", + cell: (row) =>
{row.getValue()}
, + }), + + columnHelper.accessor("valuationCap", { + header: "Valuation Cap", + cell: (row) =>
{row.getValue()}
, + }), + + columnHelper.accessor("type", { + header: "Type", + cell: (row) =>
{row.getValue()}
, + }), + + columnHelper.accessor("status", { + header: "Status", + cell: (row) =>
{row.getValue()}
, + }), + + columnHelper.accessor("discountRate", { + header: "Discount Rate", + cell: (row) =>
{row.getValue()}
, + }), + columnHelper.accessor("issueDate", { + header: "Issue Date", + cell: (row) => ( +
+ {new Date(row.getValue()).toLocaleDateString()}
), - }, - { + }), + columnHelper.display({ id: "Documents", + enableSorting: false, enableHiding: false, - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, + header: "Documents", cell: ({ row }) => { const openFileOnTab = async (key: string) => { const fileUrl = await getPresignedGetUrl(key); @@ -224,9 +160,10 @@ export const columns: ColumnDef[] = [ ); }, - }, - { + }), + columnHelper.display({ id: "actions", + enableSorting: false, enableHiding: false, cell: ({ row }) => { const router = useRouter(); @@ -278,7 +215,7 @@ export const columns: ColumnDef[] = [ ); }, - }, + }), ]; export const SafeTable = ({ safes }: SafesType) => { diff --git a/src/components/securities/options/option-table.tsx b/src/components/securities/options/option-table.tsx index 7794968f3..2e84e78cd 100644 --- a/src/components/securities/options/option-table.tsx +++ b/src/components/securities/options/option-table.tsx @@ -1,6 +1,6 @@ "use client"; -import type { ColumnDef } from "@tanstack/react-table"; +import { type ColumnDef, createColumnHelper } from "@tanstack/react-table"; import * as React from "react"; import { Button } from "@/components/ui/button"; @@ -19,7 +19,6 @@ import { api } from "@/trpc/react"; import { Avatar, AvatarImage } from "@/components/ui/avatar"; import { DataTable } from "@/components/ui/data-table/data-table"; import { DataTableBody } from "@/components/ui/data-table/data-table-body"; -import { SortButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableContent } from "@/components/ui/data-table/data-table-content"; import { DataTableHeader } from "@/components/ui/data-table/data-table-header"; import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination"; @@ -37,8 +36,10 @@ type OptionsType = { options: Option; }; -export const columns: ColumnDef[] = [ - { +const columnHelper = createColumnHelper(); + +export const columns = [ + columnHelper.display({ id: "select", header: ({ table }) => ( [] = [ ), enableSorting: false, enableHiding: false, - }, - { - id: "stakeholderName", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - accessorFn: (row) => row?.stakeholder?.name, - cell: ({ row }) => ( + }), + columnHelper.accessor("stakeholder.name", { + header: "Stakeholder", + cell: (row) => (
-

{row?.original?.stakeholder?.name}

+

{row.getValue()}

), - }, - { - accessorKey: "quantity", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => ( -
{row.getValue("quantity")}
- ), - }, - { - accessorKey: "grantId", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => ( -
{row.getValue("grantId")}
- ), - }, - { - accessorKey: "exercisePrice", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => ( -
{row.getValue("exercisePrice")}
- ), - }, - { - accessorKey: "type", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) =>
{row.getValue("type")}
, - }, - { - accessorKey: "status", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => ( -
{row.getValue("status")}
- ), - }, - { - accessorKey: "issueDate", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => ( -
- {new Date().toLocaleDateString(row.getValue("issueDate"))} + }), + columnHelper.accessor("quantity", { + header: "Quantity", + cell: (row) =>
{row.getValue()}
, + }), + columnHelper.accessor("grantId", { + header: "GrantId", + cell: (row) =>
{row.getValue()}
, + }), + columnHelper.accessor("exercisePrice", { + header: "Exercise Price", + cell: (row) =>
{row.getValue()}
, + }), + columnHelper.accessor("type", { + header: "Type", + cell: (row) =>
{row.getValue()}
, + }), + columnHelper.accessor("status", { + header: "Status", + cell: (row) =>
{row.getValue()}
, + }), + columnHelper.accessor("issueDate", { + header: "Issue Date", + cell: (row) => ( +
+ {new Date(row.getValue()).toLocaleDateString()}
), - }, - { + }), + columnHelper.display({ id: "Documents", enableHiding: false, - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, + enableSorting: false, + header: "Documents", cell: ({ row }) => { const openFileOnTab = async (key: string) => { const fileUrl = await getPresignedGetUrl(key); @@ -216,10 +146,11 @@ export const columns: ColumnDef[] = [ ); }, - }, - { + }), + columnHelper.display({ id: "actions", enableHiding: false, + enableSorting: false, cell: ({ row }) => { const router = useRouter(); const option = row.original; @@ -270,7 +201,7 @@ export const columns: ColumnDef[] = [ ); }, - }, + }), ]; const OptionTable = ({ options }: OptionsType) => { diff --git a/src/components/securities/shares/share-table.tsx b/src/components/securities/shares/share-table.tsx index 40dc7e6ee..72768750d 100644 --- a/src/components/securities/shares/share-table.tsx +++ b/src/components/securities/shares/share-table.tsx @@ -1,6 +1,6 @@ "use client"; -import type { ColumnDef } from "@tanstack/react-table"; +import { type ColumnDef, createColumnHelper } from "@tanstack/react-table"; import * as React from "react"; import { dayjsExt } from "@/common/dayjs"; @@ -15,7 +15,6 @@ import { DataTablePagination } from "@/components/ui/data-table/data-table-pagin import type { RouterOutputs } from "@/trpc/shared"; import { Button } from "@/components/ui/button"; -import { SortButton } from "@/components/ui/data-table/data-table-buttons"; import { DropdownMenuContent, DropdownMenuItem, @@ -23,7 +22,8 @@ import { DropdownMenuSeparator, } from "@/components/ui/dropdown-menu"; import { useDataTable } from "@/hooks/use-data-table"; -import { formatCurrency, formatNumber } from "@/lib/utils"; +import { cn, formatCurrency, formatNumber } from "@/lib/utils"; +import type { SecuritiesStatusEnum } from "@/prisma/enums"; import { getPresignedGetUrl } from "@/server/file-uploads"; import { api } from "@/trpc/react"; import { @@ -41,38 +41,24 @@ type SharesType = { shares: Share; }; -const humanizeShareStatus = (type: string) => { - switch (type) { - case "ACTIVE": - return "Active"; - case "DRAFT": - return "Draft"; - case "SIGNED": - return "Signed"; - case "PENDING": - return "Pending"; - default: - return ""; - } +const statusHumanizeMap: Record = { + ACTIVE: "Active", + DRAFT: "Draft", + SIGNED: "Signed", + PENDING: "Pending", }; -const StatusColorProvider = (type: string) => { - switch (type) { - case "ACTIVE": - return "bg-green-50 text-green-600 ring-green-600/20"; - case "DRAFT": - return "bg-yellow-50 text-yellow-600 ring-yellow-600/20"; - case "SIGNED": - return "bg-blue-50 text-blue-600 ring-blue-600/20"; - case "PENDING": - return "bg-gray-50 text-gray-600 ring-gray-600/20"; - default: - return ""; - } +const statusColorMap: Record = { + ACTIVE: "bg-green-50 text-green-600 ring-green-600/20", + DRAFT: "bg-yellow-50 text-yellow-600 ring-yellow-600/20", + SIGNED: "bg-blue-50 text-blue-600 ring-blue-600/20", + PENDING: "bg-gray-50 text-gray-600 ring-gray-600/20", }; -export const columns: ColumnDef[] = [ - { +const columnHelper = createColumnHelper(); + +const columns = [ + columnHelper.display({ id: "select", header: ({ table }) => ( [] = [ ), enableSorting: false, enableHiding: false, - }, - { - id: "stakeholderName", - accessorKey: "stakeholder.name", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => ( + }), + columnHelper.accessor("stakeholder.name", { + header: "Stakeholder", + cell: (row) => (
-

{row?.original?.stakeholder?.name}

+

{row.getValue()}

), - }, - { - id: "status", - accessorKey: "status", - header: ({ column }) => ( - column.toggleSorting(column?.getIsSorted() === "asc")} - /> - ), - cell: ({ row }) => { - const status = row.original?.status; + }), + columnHelper.accessor("status", { + header: "Status", + cell: (row) => { + const status = row.getValue(); return ( - {humanizeShareStatus(status)} + {statusHumanizeMap[status]} ); }, - }, - { - id: "shareClass", - accessorKey: "shareClass.classType", - - header: ({ column }) => ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ), - cell: ({ row }) => ( -
{row.original.shareClass.classType}
- ), - }, - { - id: "quantity", - accessorKey: "quantity", - header: ({ column }) => ( -
- column.toggleSorting(column.getIsSorted() === "asc")} - /> -
- ), - cell: ({ row }) => { - const quantity = row.original.quantity; + }), + columnHelper.accessor("shareClass.classType", { + header: "Share class", + cell: (row) =>
{row.getValue()}
, + }), + columnHelper.accessor("quantity", { + header: "Quantity", + cell: (row) => { + const quantity = row.getValue(); return (
{quantity ? formatNumber(quantity) : null}
); }, - }, - { - id: "pricePerShare", - accessorKey: "pricePerShare", - header: ({ column }) => ( -
- column.toggleSorting(column.getIsSorted() === "asc")} - /> -
- ), - cell: ({ row }) => { - const price = row.original.pricePerShare; + }), + columnHelper.accessor("pricePerShare", { + header: "Unit price", + cell: (row) => { + const price = row.getValue(); return (
{price ? formatCurrency(price, "USD") : null}
); }, - }, - { - id: "issueDate", - accessorKey: "issueDate", - header: ({ column }) => ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ), - cell: ({ row }) => ( -
- {dayjsExt(row.original.issueDate).format("DD/MM/YYYY")} + }), + columnHelper.accessor("issueDate", { + header: "Issued", + cell: (row) => ( +
+ {dayjsExt(row.getValue()).format("DD/MM/YYYY")}
), - }, - { - id: "boardApprovalDate", - accessorKey: "boardApprovalDate", - header: ({ column }) => ( -
- column.toggleSorting(column.getIsSorted() === "asc")} - /> + }), + columnHelper.accessor("boardApprovalDate", { + header: "Board Approved", + cell: (row) => ( +
+ {dayjsExt(row.getValue()).format("DD/MM/YYYY")}
), - cell: ({ row }) => ( -
- {dayjsExt(row.original.boardApprovalDate).format("DD/MM/YYYY")} -
- ), - }, - { + }), + columnHelper.display({ id: "Documents", enableHiding: false, - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, + enableSorting: false, + header: "Board Approved", cell: ({ row }) => { const documents = row?.original?.documents; @@ -276,10 +198,12 @@ export const columns: ColumnDef[] = [ ); }, - }, - { + }), + + columnHelper.display({ id: "actions", enableHiding: false, + enableSorting: false, cell: ({ row }) => { // eslint-disable-next-line react-hooks/rules-of-hooks const router = useRouter(); @@ -332,7 +256,7 @@ export const columns: ColumnDef[] = [ ); }, - }, + }), ]; const ShareTable = ({ shares }: SharesType) => { diff --git a/src/components/security/passkey/user-passkeys-data-table.tsx b/src/components/security/passkey/user-passkeys-data-table.tsx index 530a700de..dd2e315d6 100644 --- a/src/components/security/passkey/user-passkeys-data-table.tsx +++ b/src/components/security/passkey/user-passkeys-data-table.tsx @@ -4,7 +4,6 @@ import { dayjsExt } from "@/common/dayjs"; import { Badge } from "@/components/ui/badge"; import { DataTable } from "@/components/ui/data-table/data-table"; import { DataTableBody } from "@/components/ui/data-table/data-table-body"; -import { SortButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableContent } from "@/components/ui/data-table/data-table-content"; import { DataTableHeader } from "@/components/ui/data-table/data-table-header"; import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination"; @@ -12,7 +11,7 @@ import { useDataTable } from "@/hooks/use-data-table"; import { api } from "@/trpc/react"; import type { RouterOutputs } from "@/trpc/shared"; import { RiMore2Fill } from "@remixicon/react"; -import type { ColumnDef } from "@tanstack/react-table"; +import { type ColumnDef, createColumnHelper } from "@tanstack/react-table"; import { useRouter } from "next/navigation"; import * as React from "react"; import { toast } from "sonner"; @@ -47,94 +46,52 @@ const humanizeDeviceType = (type: string) => { } }; -export const columns: ColumnDef[] = [ - { - accessorKey: "name", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) =>
{row.getValue("name")}
, - }, - { - accessorKey: "credentialDeviceType", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => ( -
- {humanizeDeviceType(row.getValue("credentialDeviceType"))} -
+const columnHelper = createColumnHelper(); + +const columns = [ + columnHelper.accessor("name", { + header: "Device Name", + cell: (row) =>
{row.getValue()}
, + }), + columnHelper.accessor("credentialDeviceType", { + header: "Device type", + cell: (row) => ( +
{humanizeDeviceType(row.getValue())}
), - }, - { - accessorKey: "credentialBackedUp", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => ( + }), + columnHelper.accessor("credentialBackedUp", { + header: "Backup", + cell: (row) => (
- {row.getValue("credentialBackedUp") ? "Done" : "Pending"} + {row.getValue() ? "Done" : "Pending"}
), - }, - { - accessorKey: "createdAt", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => ( -
- {dayjsExt(row.getValue("createdAt")).fromNow()} + }), + columnHelper.accessor("createdAt", { + header: "Created", + cell: (row) => ( +
+ {dayjsExt(row.getValue()).fromNow()}
), - }, - - { - accessorKey: "lastUsedAt", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - cell: ({ row }) => ( -
- {row.getValue("lastUsedAt") - ? dayjsExt(row.getValue("lastUsedAt")).fromNow() - : "Not yet"} + }), + columnHelper.accessor("lastUsedAt", { + header: "Used", + cell: (row) => ( +
+ {row.getValue() ? dayjsExt(row.getValue()).fromNow() : "Not yet"}
), - }, - { + }), + columnHelper.display({ id: "actions", enableHiding: false, + enableSorting: false, cell: ({ row }) => { const [open, setOpen] = React.useState(false); const router = useRouter(); @@ -200,7 +157,7 @@ export const columns: ColumnDef[] = [ ); }, - }, + }), ]; const PasskeyTable = ({ passkey }: PasskeyType) => { diff --git a/src/components/ui/data-table/data-table-buttons.tsx b/src/components/ui/data-table/data-table-buttons.tsx index e3123a8f3..16fb2294d 100644 --- a/src/components/ui/data-table/data-table-buttons.tsx +++ b/src/components/ui/data-table/data-table-buttons.tsx @@ -1,21 +1,7 @@ import { cn } from "@/lib/utils"; -import { RiCloseLine, RiExpandUpDownLine } from "@remixicon/react"; +import { RiCloseLine } from "@remixicon/react"; import { Button, type ButtonProps } from "../button"; -interface SortButtonProps - extends Omit, "children"> { - label: string; -} - -export function SortButton({ label, className, ...rest }: SortButtonProps) { - return ( - - ); -} - type ResetButtonProps = Omit; export function ResetButton({ className, ...rest }: ResetButtonProps) { diff --git a/src/components/update/update-table.tsx b/src/components/update/update-table.tsx index c920013e2..454b658e1 100644 --- a/src/components/update/update-table.tsx +++ b/src/components/update/update-table.tsx @@ -5,7 +5,6 @@ import { Button } from "@/components/ui/button"; import { Checkbox } from "@/components/ui/checkbox"; import { DataTable } from "@/components/ui/data-table/data-table"; import { DataTableBody } from "@/components/ui/data-table/data-table-body"; -import { SortButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableContent } from "@/components/ui/data-table/data-table-content"; import { DataTableHeader } from "@/components/ui/data-table/data-table-header"; import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination"; @@ -16,12 +15,13 @@ import { DropdownMenuTrigger, } from "@/components/ui/dropdown-menu"; import { useDataTable } from "@/hooks/use-data-table"; +import type { UpdateStatusEnum } from "@/prisma/enums"; import type { RouterOutputs } from "@/trpc/shared"; import { RiAddCircleLine } from "@remixicon/react"; -import type { ColumnDef } from "@tanstack/react-table"; +import { type ColumnDef, createColumnHelper } from "@tanstack/react-table"; import { useSession } from "next-auth/react"; import Link from "next/link"; -import React, { useState } from "react"; +import React, { type ReactElement, useState } from "react"; import { pushModal } from "../modals"; import { ChangeUpdateVisibilityAlertDialog } from "./change-update-visibility-alert-dialog"; import { UpdateTableToolbar } from "./update-table-toolbar"; @@ -32,15 +32,10 @@ type UpdateTableType = { updates: Update; }; -const getUpdateStatus = (status: string) => { - switch (status) { - case "DRAFT": - return Draft; - case "PUBLIC": - return Public; - case "PRIVATE": - return Private; - } +const UpdateStatus: Record = { + DRAFT: Draft, + PUBLIC: Public, + PRIVATE: Private, }; const UpdateActions = (row: { original: Update[number] }) => { @@ -108,8 +103,10 @@ const UpdateActions = (row: { original: Update[number] }) => { ); }; -export const columns: ColumnDef[] = [ - { +const columnHelper = createColumnHelper(); + +const columns = [ + columnHelper.display({ id: "select", header: ({ table }) => ( [] = [ ), enableSorting: false, enableHiding: false, - }, - { - id: "title", - header: ({ column }) => { - return ( - column.toggleSorting(column.getIsSorted() === "asc")} - /> - ); - }, - accessorFn: (row) => row.title, - cell: ({ row }) => ( -
{row.getValue("title")}
- ), - }, - { - accessorKey: "status", - header: () => { - return
Sharing status
; - }, - cell: ({ row }) =>
{getUpdateStatus(row.original.status)}
, - }, - { - accessorKey: "actions", - header: () => { - return
Actions
; - }, - cell: ({ row }) =>
{UpdateActions(row)}
, - }, + }), + + columnHelper.accessor("title", { + header: "Title", + cell: (row) =>
{row.getValue()}
, + }), + columnHelper.accessor("status", { + header: "Status", + cell: (row) =>
{UpdateStatus[row.getValue()]}
, + }), + columnHelper.display({ + id: "actions", + header: "Title", + cell: ({ row }) => UpdateActions(row), + enableSorting: false, + enableHiding: false, + }), ]; const UpdateTable = ({ updates }: UpdateTableType) => { From 6c5d5f1ab9085dc24b63f708d37c722dbeb00fd0 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Fri, 30 Aug 2024 20:04:47 +0530 Subject: [PATCH 25/34] feat: add seed --- prisma/seeds/companies.ts | 12 +++-- prisma/seeds/index.ts | 99 ++++++++++++++++++++++++++----------- prisma/seeds/stakeholder.ts | 45 +++++++++++++++++ prisma/seeds/team.ts | 13 +++-- 4 files changed, 131 insertions(+), 38 deletions(-) create mode 100644 prisma/seeds/stakeholder.ts diff --git a/prisma/seeds/companies.ts b/prisma/seeds/companies.ts index 4f24b99bf..1c02b7195 100644 --- a/prisma/seeds/companies.ts +++ b/prisma/seeds/companies.ts @@ -1,5 +1,5 @@ import { generatePublicId } from "@/common/id"; -import { db } from "@/server/db"; +import type { TPrismaOrTransaction } from "@/server/db"; import { faker } from "@faker-js/faker"; import colors from "colors"; import { sample } from "lodash-es"; @@ -19,7 +19,7 @@ type CompanyType = { zipcode: string; country: string; }; -const seedCompanies = async (count = 4) => { +const seedCompanies = async (tx: TPrismaOrTransaction, count = 4) => { const companies: CompanyType[] = []; for (let i = 0; i < count; i++) { @@ -41,11 +41,15 @@ const seedCompanies = async (count = 4) => { console.log(`Seeding ${companies.length} companies`.blue); - const records = await db.company.createMany({ + const records = await tx.company.createManyAndReturn({ data: companies, + select: { + id: true, + name: true, + }, }); - console.log(`🎉 Seeded ${records.count} companies`.green); + console.log(`🎉 Seeded ${records.length} companies`.green); return records; }; diff --git a/prisma/seeds/index.ts b/prisma/seeds/index.ts index b7688b9df..471291d7e 100644 --- a/prisma/seeds/index.ts +++ b/prisma/seeds/index.ts @@ -3,8 +3,10 @@ import colors from "colors"; import inquirer from "inquirer"; colors.enable(); +import { Prisma } from "@prisma/client"; import type { QuestionCollection } from "inquirer"; import seedCompanies from "./companies"; +import { seedStakeholders } from "./stakeholder"; import seedTeam from "./team"; if (process.env.NODE_ENV === "production") { @@ -23,47 +25,86 @@ const seed = async () => { const answer = inquiry.answer as boolean; if (answer) { - await nuke(); + await cleanupDb(); console.log("Seeding database".underline.cyan); - return db.$transaction(async () => { - await seedCompanies(); - await seedTeam(); + return db.$transaction(async (tx) => { + const companies = await seedCompanies(tx); + await seedTeam(tx); + + for (const company of companies) { + await seedStakeholders(tx, company.id, company.name, 500); + } }); - } else { - throw new Error("Seeding aborted"); } -}; - -const nuke = async () => { - console.log("🚀 Nuking database records".yellow); - return db.$transaction(async (db) => { - await db.user.deleteMany(); - await db.member.deleteMany(); - await db.company.deleteMany(); - await db.shareClass.deleteMany(); - await db.equityPlan.deleteMany(); - await db.document.deleteMany(); - await db.bucket.deleteMany(); - await db.audit.deleteMany(); - await db.session.deleteMany(); - }); + throw new Error("Seeding aborted"); }; await seed() - .then(async () => { + .then(() => { console.log("✅ Database seeding completed".green); console.log( - `💌 We have created four admin accounts for you. Please login with one of these emails:\n` + "💌 We have created four admin accounts for you. Please login with one of these emails:\n" .cyan, - `ceo@example.com\n`.underline.yellow, - `cto@example.com\n`.underline.yellow, - `cfo@example.com\n`.underline.yellow, - `lawyer@example.com\n`.underline.yellow, + "ceo@example.com\n".underline.yellow, + "cto@example.com\n".underline.yellow, + "cfo@example.com\n".underline.yellow, + "lawyer@example.com\n".underline.yellow, ); - await db.$disconnect(); }) - .catch(async (error: Error) => { + .catch((error: Error) => { console.log(`❌ ${error.message}`.red); + }) + .finally(async () => { await db.$disconnect(); }); + +export async function cleanupDb() { + console.log("🚀 Nuking database records".yellow); + + try { + const tables = await db.$queryRaw<{ tablename: string }[]>` + SELECT tablename FROM pg_tables + WHERE schemaname = 'public' AND tablename != '_prisma_migrations' + `; + + // Disable foreign key checks + await db.$executeRaw`SET CONSTRAINTS ALL DEFERRED`; + + for (const { tablename } of tables) { + try { + // Check if the table exists before attempting to truncate + const tableExists = await db.$queryRaw<[{ exists: boolean }]>` + SELECT EXISTS ( + SELECT FROM information_schema.tables + WHERE table_schema = 'public' AND table_name = ${tablename} + ) + `; + + if (tableExists[0].exists) { + await db.$executeRaw( + Prisma.sql`TRUNCATE TABLE "${Prisma.raw(tablename)}" CASCADE`, + ); + console.log(`Table ${tablename} truncated successfully`.green); + } else { + console.log(`Table ${tablename} doesn't exist, skipping`.yellow); + } + } catch (err) { + if (err instanceof Prisma.PrismaClientKnownRequestError) { + console.error(`Error truncating table ${tablename}:`, err.message); + } else { + console.error(`Unexpected error truncating table ${tablename}:`, err); + } + } + } + + // Re-enable foreign key checks + await db.$executeRaw`SET CONSTRAINTS ALL IMMEDIATE`; + + console.log( + "All tables reset successfully, except _prisma_migrations".yellow, + ); + } catch (error) { + console.error("Error resetting tables:", error); + } +} diff --git a/prisma/seeds/stakeholder.ts b/prisma/seeds/stakeholder.ts new file mode 100644 index 000000000..61500dd9f --- /dev/null +++ b/prisma/seeds/stakeholder.ts @@ -0,0 +1,45 @@ +import type { TPrismaOrTransaction } from "@/server/db"; +import { faker } from "@faker-js/faker"; +import colors from "colors"; +import { StakeholderRelationshipEnum, StakeholderTypeEnum } from "../enums"; +colors.enable(); + +export async function seedStakeholders( + tx: TPrismaOrTransaction, + companyId: string, + companyName: string, + count = 100, +) { + const stakeholders = []; + + for (let index = 0; index < count; index++) { + stakeholders.push({ + name: faker.person.fullName(), + email: faker.internet.email(), + institutionName: faker.company.name(), + stakeholderType: faker.helpers.arrayElement( + Object.values(StakeholderTypeEnum), + ), + currentRelationship: faker.helpers.arrayElement( + Object.values(StakeholderRelationshipEnum), + ), + taxId: faker.finance.accountNumber(), + + streetAddress: faker.location.streetAddress(), + city: faker.location.city(), + state: faker.location.state(), + zipcode: faker.location.zipCode(), + country: faker.location.country(), + companyId, + }); + } + console.log( + `Seeding ${stakeholders.length} stakeholders for ${companyName}`.blue, + ); + + const record = await tx.stakeholder.createMany({ data: stakeholders }); + + console.log( + `🎉 Seeded ${record.count} stakeholders for ${companyName}`.green, + ); +} diff --git a/prisma/seeds/team.ts b/prisma/seeds/team.ts index 13596205e..bd75861a7 100644 --- a/prisma/seeds/team.ts +++ b/prisma/seeds/team.ts @@ -1,5 +1,6 @@ import type { MemberStatusEnum } from "@/prisma/enums"; -import { db } from "@/server/db"; +import type { TPrismaOrTransaction } from "@/server/db"; + import { faker } from "@faker-js/faker"; import bcrypt from "bcryptjs"; import colors from "colors"; @@ -14,7 +15,7 @@ type UserType = { status?: MemberStatusEnum; }; -const seedTeam = async () => { +const seedTeam = async (tx: TPrismaOrTransaction) => { const team = [ { name: faker.person.fullName(), @@ -60,14 +61,15 @@ const seedTeam = async () => { ]; console.log(`Seeding ${team.length} team members`.blue); - const companies = await db.company.findMany(); + const companies = await tx.company.findMany(); + // biome-ignore lint/complexity/noForEach: team.forEach(async (t) => { // const { name, email, image, title, status, isOnboarded } = t const salt = await bcrypt.genSalt(10); const hashedPassword = await bcrypt.hash("P@ssw0rd!", salt); const { name, email, title, status, isOnboarded } = t; - const user = await db.user.create({ + const user = await tx.user.create({ data: { name, email, @@ -77,8 +79,9 @@ const seedTeam = async () => { }, }); + // biome-ignore lint/complexity/noForEach: companies.forEach(async (company) => { - await db.member.create({ + await tx.member.create({ data: { title, isOnboarded, From 92d651df0d97c6e5de5444118718469e5a86247a Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Mon, 2 Sep 2024 20:55:12 +0530 Subject: [PATCH 26/34] chore: add tax id --- src/server/api/schema/stakeholder.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/server/api/schema/stakeholder.ts b/src/server/api/schema/stakeholder.ts index f01aa8d30..f2e21154a 100644 --- a/src/server/api/schema/stakeholder.ts +++ b/src/server/api/schema/stakeholder.ts @@ -36,6 +36,11 @@ export const StakeholderSchema = z example: "ACME Corp", }), + taxId: z.string().nullish().openapi({ + description: "Tax id", + example: "12-3456789", + }), + stakeholderType: z.enum(StakeholderTypeArray).openapi({ description: "Stakeholder type", example: "INDIVIDUAL", From e73353182389bdb46e4c4805e567eb62dc499f33 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Mon, 2 Sep 2024 21:41:10 +0530 Subject: [PATCH 27/34] chore: upgrade trpc --- package.json | 12 +- pnpm-lock.yaml | 353 ++++++++++++++++++++++++------------------------- 2 files changed, 176 insertions(+), 189 deletions(-) diff --git a/package.json b/package.json index d975709e3..01f7b655f 100644 --- a/package.json +++ b/package.json @@ -67,13 +67,13 @@ "@sindresorhus/slugify": "^2.2.1", "@stripe/stripe-js": "^4.1.0", "@t3-oss/env-nextjs": "^0.10.1", - "@tanstack/react-query": "^4.36.1", + "@tanstack/react-query": "^5.53.3", "@tanstack/react-table": "^8.20.1", "@tremor/react": "^3.17.4", - "@trpc/client": "^10.43.6", - "@trpc/next": "^10.45.2", - "@trpc/react-query": "^10.43.6", - "@trpc/server": "^10.43.6", + "@trpc/client": "11.0.0-rc.498", + "@trpc/next": "11.0.0-rc.498", + "@trpc/react-query": "11.0.0-rc.498", + "@trpc/server": "11.0.0-rc.498", "@types/bcryptjs": "^2.4.6", "@types/papaparse": "^5.3.14", "@wojtekmaj/react-hooks": "^1.20.0", @@ -147,7 +147,7 @@ "prisma": "^5.13.0", "tailwindcss": "^3.4.3", "tsx": "^4.7.0", - "typescript": "^5.4.5", + "typescript": "^5.5.3", "vitest": "^1.6.0" }, "ct3aMetadata": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 863d8c735..97ecf9eac 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -100,7 +100,7 @@ importers: version: 4.2.0(react@18.3.1) '@scalar/nextjs-api-reference': specifier: ^0.4.18 - version: 0.4.18(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(postcss@8.4.40)(react-dom@18.2.0(react@18.3.1))(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + version: 0.4.18(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(postcss@8.4.40)(react-dom@18.2.0(react@18.3.1))(tailwindcss@3.4.3)(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) '@sentry/nextjs': specifier: ^8.19.0 version: 8.19.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0))(next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react@18.3.1)(webpack@5.92.0) @@ -118,10 +118,10 @@ importers: version: 4.1.0 '@t3-oss/env-nextjs': specifier: ^0.10.1 - version: 0.10.1(typescript@5.4.5)(zod@3.23.8) + version: 0.10.1(typescript@5.5.4)(zod@3.23.8) '@tanstack/react-query': - specifier: ^4.36.1 - version: 4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + specifier: ^5.53.3 + version: 5.53.3(react@18.3.1) '@tanstack/react-table': specifier: ^8.20.1 version: 8.20.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1) @@ -129,17 +129,17 @@ importers: specifier: ^3.17.4 version: 3.17.4(react-dom@18.2.0(react@18.3.1))(react@18.3.1)(tailwindcss@3.4.3) '@trpc/client': - specifier: ^10.43.6 - version: 10.45.2(@trpc/server@10.45.2) + specifier: 11.0.0-rc.498 + version: 11.0.0-rc.498(@trpc/server@11.0.0-rc.498) '@trpc/next': - specifier: ^10.45.2 - version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/server@10.45.2)(next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + specifier: 11.0.0-rc.498 + version: 11.0.0-rc.498(@tanstack/react-query@5.53.3(react@18.3.1))(@trpc/client@11.0.0-rc.498(@trpc/server@11.0.0-rc.498))(@trpc/react-query@11.0.0-rc.498(@tanstack/react-query@5.53.3(react@18.3.1))(@trpc/client@11.0.0-rc.498(@trpc/server@11.0.0-rc.498))(@trpc/server@11.0.0-rc.498)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/server@11.0.0-rc.498)(next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react-dom@18.2.0(react@18.3.1))(react@18.3.1) '@trpc/react-query': - specifier: ^10.43.6 - version: 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) + specifier: 11.0.0-rc.498 + version: 11.0.0-rc.498(@tanstack/react-query@5.53.3(react@18.3.1))(@trpc/client@11.0.0-rc.498(@trpc/server@11.0.0-rc.498))(@trpc/server@11.0.0-rc.498)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) '@trpc/server': - specifier: ^10.43.6 - version: 10.45.2 + specifier: 11.0.0-rc.498 + version: 11.0.0-rc.498 '@types/bcryptjs': specifier: ^2.4.6 version: 2.4.6 @@ -223,7 +223,7 @@ importers: version: 0.7.4(@prisma/client@5.14.0(prisma@5.14.0)) prisma-json-types-generator: specifier: ^3.0.4 - version: 3.0.4(prisma@5.14.0)(typescript@5.4.5) + version: 3.0.4(prisma@5.14.0)(typescript@5.5.4) pushmodal: specifier: ^1.0.4 version: 1.0.4(@radix-ui/react-dialog@1.0.5(@types/react-dom@18.3.0)(@types/react@18.3.3)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react-dom@18.2.0(react@18.3.1))(react@18.3.1) @@ -338,7 +338,7 @@ importers: version: 9.2.22 knip: specifier: ^5.17.2 - version: 5.17.4(@types/node@20.12.12)(typescript@5.4.5) + version: 5.17.4(@types/node@20.12.12)(typescript@5.5.4) lint-staged: specifier: ^15.2.2 version: 15.2.2 @@ -355,8 +355,8 @@ importers: specifier: ^4.7.0 version: 4.10.4 typescript: - specifier: ^5.4.5 - version: 5.4.5 + specifier: ^5.5.3 + version: 5.5.4 vitest: specifier: ^1.6.0 version: 1.6.0(@types/node@20.12.12)(terser@5.31.1) @@ -3601,20 +3601,13 @@ packages: peerDependencies: tailwindcss: '>=3.0.0 || insiders' - '@tanstack/query-core@4.36.1': - resolution: {integrity: sha512-DJSilV5+ytBP1FbFcEJovv4rnnm/CokuVvrBEtW/Va9DvuJ3HksbXUJEpI0aV1KtuL4ZoO9AVE6PyNLzF7tLeA==} + '@tanstack/query-core@5.53.3': + resolution: {integrity: sha512-ZfjAgd7NpqDx0e4aYBt7EmS2enbulPrJwowTy+mayRE93WUUH+sIYHun1TdRjpGwDPMNNZ5D6goh7n3CwoO+HA==} - '@tanstack/react-query@4.36.1': - resolution: {integrity: sha512-y7ySVHFyyQblPl3J3eQBWpXZkliroki3ARnBKsdJchlgt7yJLRDUcf4B8soufgiYt3pEQIkBWBx1N9/ZPIeUWw==} + '@tanstack/react-query@5.53.3': + resolution: {integrity: sha512-286mN/91CeM7vC6CZFLKYDHSw+WyMX6ekIvzoTbpM4xyPb99VSyCKPLyPgaOatKqYm6ooMBquSq9NGRdKgsJfg==} peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - react-native: '*' - peerDependenciesMeta: - react-dom: - optional: true - react-native: - optional: true + react: ^18 || ^19 '@tanstack/react-table@8.20.1': resolution: {integrity: sha512-PJK+07qbengObe5l7c8vCdtefXm8cyR4i078acWrHbdm8JKw1ES7YpmOtVt9ALUVEEFAHscdVpGRhRgikgFMbQ==} @@ -3807,33 +3800,38 @@ packages: react: ^18.0.0 react-dom: '>=16.6.0' - '@trpc/client@10.45.2': - resolution: {integrity: sha512-ykALM5kYWTLn1zYuUOZ2cPWlVfrXhc18HzBDyRhoPYN0jey4iQHEFSEowfnhg1RvYnrAVjNBgHNeSAXjrDbGwg==} + '@trpc/client@11.0.0-rc.498': + resolution: {integrity: sha512-a1VjvLHCo8gPENMfzI8lVF1ys6kOGI3f/cIAZUMB0d2TlkDERlFYANnSnzFvmiKy6ICh3lsQQ3OIMmIfyd8OtQ==} peerDependencies: - '@trpc/server': 10.45.2 + '@trpc/server': 11.0.0-rc.498+5714423cc - '@trpc/next@10.45.2': - resolution: {integrity: sha512-RSORmfC+/nXdmRY1pQ0AalsVgSzwNAFbZLYHiTvPM5QQ8wmMEHilseCYMXpu0se/TbPt9zVR6Ka2d7O6zxKkXg==} + '@trpc/next@11.0.0-rc.498': + resolution: {integrity: sha512-GoaRReGOd949aZbzJI8xv+QFcLUpp7+BOw5NdgLjEk47Nin2TcLl19Tk8JhXImFzwDLF8JQFrHvk91mnb6ka0Q==} peerDependencies: - '@tanstack/react-query': ^4.18.0 - '@trpc/client': 10.45.2 - '@trpc/react-query': 10.45.2 - '@trpc/server': 10.45.2 + '@tanstack/react-query': ^5.49.2 + '@trpc/client': 11.0.0-rc.498+5714423cc + '@trpc/react-query': 11.0.0-rc.498+5714423cc + '@trpc/server': 11.0.0-rc.498+5714423cc next: '*' react: '>=16.8.0' react-dom: '>=16.8.0' + peerDependenciesMeta: + '@tanstack/react-query': + optional: true + '@trpc/react-query': + optional: true - '@trpc/react-query@10.45.2': - resolution: {integrity: sha512-BAqb9bGZIscroradlNx+Cc9522R+idY3BOSf5z0jHUtkxdMbjeGKxSSMxxu7JzoLqSIEC+LVzL3VvF8sdDWaZQ==} + '@trpc/react-query@11.0.0-rc.498': + resolution: {integrity: sha512-il/fgO9DSTHmrJbDInGNl8PymLo19CEfIjBXmerTm4hg/p438fQb4ZKDVbpBxEThgAaaP6D0HTELWn5VDWG3VQ==} peerDependencies: - '@tanstack/react-query': ^4.18.0 - '@trpc/client': 10.45.2 - '@trpc/server': 10.45.2 - react: '>=16.8.0' - react-dom: '>=16.8.0' + '@tanstack/react-query': ^5.49.2 + '@trpc/client': 11.0.0-rc.498+5714423cc + '@trpc/server': 11.0.0-rc.498+5714423cc + react: '>=18.2.0' + react-dom: '>=18.2.0' - '@trpc/server@10.45.2': - resolution: {integrity: sha512-wOrSThNNE4HUnuhJG6PfDRp4L2009KDVxsd+2VYH8ro6o/7/jwYZ8Uu5j+VaW+mOmc8EHerHzGcdbGNQSAUPgg==} + '@trpc/server@11.0.0-rc.498': + resolution: {integrity: sha512-LECCOz8JNB67CBasPA6fkLbhJIbUQRP1wq02xUfUMeaSEZFWJL+7fcTgLaWFQSHMMnMsvkLuDuDDkUSCF2SXmg==} '@types/aria-query@5.0.4': resolution: {integrity: sha512-rfT93uj5s0PRL7EzccGMs3brplhcrghnDoV26NqKhCAS1hVo+WdNsPvE/yb6ilfr5hi2MEk6d5EWJTKdxg8jVw==} @@ -7930,8 +7928,8 @@ packages: engines: {node: '>=14.17'} hasBin: true - typescript@5.4.5: - resolution: {integrity: sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==} + typescript@5.5.4: + resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} engines: {node: '>=14.17'} hasBin: true @@ -8069,11 +8067,6 @@ packages: '@types/react': optional: true - use-sync-external-store@1.2.2: - resolution: {integrity: sha512-PElTlVMwpblvbNqQ82d2n6RjStvdSoNe9FG28kNfz3WiXilJm4DdNkEzRhCZuIDwY8U08WVihhGR5iRqAwfDiw==} - peerDependencies: - react: ^16.8.0 || ^17.0.0 || ^18.0.0 - usehooks-ts@3.1.0: resolution: {integrity: sha512-bBIa7yUyPhE1BCc0GmR96VU/15l/9gP1Ch5mYdLcFBaFGQsdmXkvjV0TtOqW1yUd6VjIwDunm+flSciCQXujiw==} engines: {node: '>=16.15.0'} @@ -8583,7 +8576,7 @@ snapshots: '@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/client-sts@3.577.0) '@aws-sdk/client-sts': 3.577.0 '@aws-sdk/core': 3.576.0 - '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0) + '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0) '@aws-sdk/middleware-bucket-endpoint': 3.577.0 '@aws-sdk/middleware-expect-continue': 3.577.0 '@aws-sdk/middleware-flexible-checksums': 3.577.0 @@ -8644,7 +8637,7 @@ snapshots: '@aws-crypto/sha256-js': 3.0.0 '@aws-sdk/client-sts': 3.577.0 '@aws-sdk/core': 3.576.0 - '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0) + '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0) '@aws-sdk/middleware-host-header': 3.577.0 '@aws-sdk/middleware-logger': 3.577.0 '@aws-sdk/middleware-recursion-detection': 3.577.0 @@ -8733,7 +8726,7 @@ snapshots: '@aws-crypto/sha256-js': 3.0.0 '@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/client-sts@3.577.0) '@aws-sdk/core': 3.576.0 - '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0) + '@aws-sdk/credential-provider-node': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0) '@aws-sdk/middleware-host-header': 3.577.0 '@aws-sdk/middleware-logger': 3.577.0 '@aws-sdk/middleware-recursion-detection': 3.577.0 @@ -8801,12 +8794,12 @@ snapshots: '@smithy/util-stream': 3.0.1 tslib: 2.6.2 - '@aws-sdk/credential-provider-ini@3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0)': + '@aws-sdk/credential-provider-ini@3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0)': dependencies: '@aws-sdk/client-sts': 3.577.0 '@aws-sdk/credential-provider-env': 3.577.0 '@aws-sdk/credential-provider-process': 3.577.0 - '@aws-sdk/credential-provider-sso': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0)) + '@aws-sdk/credential-provider-sso': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0) '@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/client-sts@3.577.0) '@aws-sdk/types': 3.577.0 '@smithy/credential-provider-imds': 3.0.0 @@ -8818,13 +8811,13 @@ snapshots: - '@aws-sdk/client-sso-oidc' - aws-crt - '@aws-sdk/credential-provider-node@3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0)': + '@aws-sdk/credential-provider-node@3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0)': dependencies: '@aws-sdk/credential-provider-env': 3.577.0 '@aws-sdk/credential-provider-http': 3.577.0 - '@aws-sdk/credential-provider-ini': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))(@aws-sdk/client-sts@3.577.0) + '@aws-sdk/credential-provider-ini': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0)(@aws-sdk/client-sts@3.577.0) '@aws-sdk/credential-provider-process': 3.577.0 - '@aws-sdk/credential-provider-sso': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0)) + '@aws-sdk/credential-provider-sso': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0) '@aws-sdk/credential-provider-web-identity': 3.577.0(@aws-sdk/client-sts@3.577.0) '@aws-sdk/types': 3.577.0 '@smithy/credential-provider-imds': 3.0.0 @@ -8845,10 +8838,10 @@ snapshots: '@smithy/types': 3.0.0 tslib: 2.6.2 - '@aws-sdk/credential-provider-sso@3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))': + '@aws-sdk/credential-provider-sso@3.577.0(@aws-sdk/client-sso-oidc@3.577.0)': dependencies: '@aws-sdk/client-sso': 3.577.0 - '@aws-sdk/token-providers': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0)) + '@aws-sdk/token-providers': 3.577.0(@aws-sdk/client-sso-oidc@3.577.0) '@aws-sdk/types': 3.577.0 '@smithy/property-provider': 3.0.0 '@smithy/shared-ini-file-loader': 3.0.0 @@ -8985,7 +8978,7 @@ snapshots: '@smithy/types': 3.0.0 tslib: 2.6.2 - '@aws-sdk/token-providers@3.577.0(@aws-sdk/client-sso-oidc@3.577.0(@aws-sdk/client-sts@3.577.0))': + '@aws-sdk/token-providers@3.577.0(@aws-sdk/client-sso-oidc@3.577.0)': dependencies: '@aws-sdk/client-sso-oidc': 3.577.0(@aws-sdk/client-sts@3.577.0) '@aws-sdk/types': 3.577.0 @@ -9736,11 +9729,11 @@ snapshots: '@floating-ui/utils@0.2.3': {} - '@floating-ui/vue@1.0.7(vue@3.4.30(typescript@5.4.5))': + '@floating-ui/vue@1.0.7(vue@3.4.30(typescript@5.5.4))': dependencies: '@floating-ui/dom': 1.6.5 '@floating-ui/utils': 0.2.3 - vue-demi: 0.14.8(vue@3.4.30(typescript@5.4.5)) + vue-demi: 0.14.8(vue@3.4.30(typescript@5.5.4)) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -9764,10 +9757,10 @@ snapshots: dependencies: tailwindcss: 3.4.3 - '@headlessui/vue@1.7.22(vue@3.4.30(typescript@5.4.5))': + '@headlessui/vue@1.7.22(vue@3.4.30(typescript@5.5.4))': dependencies: - '@tanstack/vue-virtual': 3.7.0(vue@3.4.30(typescript@5.4.5)) - vue: 3.4.30(typescript@5.4.5) + '@tanstack/vue-virtual': 3.7.0(vue@3.4.30(typescript@5.5.4)) + vue: 3.4.30(typescript@5.5.4) '@hexagon/base64@1.1.28': {} @@ -11792,14 +11785,14 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.18.0': optional: true - '@scalar/api-client-modal@0.0.16(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': + '@scalar/api-client-modal@0.0.16(tailwindcss@3.4.3)(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': dependencies: - '@scalar/client-app': 0.1.14(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) - '@scalar/components': 0.12.4(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + '@scalar/client-app': 0.1.14(tailwindcss@3.4.3)(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + '@scalar/components': 0.12.4(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) '@scalar/oas-utils': 0.2.5 '@scalar/object-utils': 1.1.2 - vue: 3.4.30(typescript@5.4.5) - vue-router: 4.4.0(vue@3.4.30(typescript@5.4.5)) + vue: 3.4.30(typescript@5.5.4) + vue-router: 4.4.0(vue@3.4.30(typescript@5.5.4)) transitivePeerDependencies: - '@jest/globals' - '@types/bun' @@ -11812,24 +11805,24 @@ snapshots: - typescript - vitest - '@scalar/api-client@1.3.19(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': + '@scalar/api-client@1.3.19(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': dependencies: - '@floating-ui/vue': 1.0.7(vue@3.4.30(typescript@5.4.5)) - '@headlessui/vue': 1.7.22(vue@3.4.30(typescript@5.4.5)) - '@scalar/components': 0.12.4(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + '@floating-ui/vue': 1.0.7(vue@3.4.30(typescript@5.5.4)) + '@headlessui/vue': 1.7.22(vue@3.4.30(typescript@5.5.4)) + '@scalar/components': 0.12.4(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) '@scalar/oas-utils': 0.2.5 '@scalar/openapi-parser': 0.7.1 - '@scalar/themes': 0.9.8(typescript@5.4.5) - '@scalar/use-codemirror': 0.11.4(typescript@5.4.5) - '@scalar/use-toasts': 0.7.4(typescript@5.4.5) - '@scalar/use-tooltip': 1.0.1(typescript@5.4.5) - '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.4.5)) + '@scalar/themes': 0.9.8(typescript@5.5.4) + '@scalar/use-codemirror': 0.11.4(typescript@5.5.4) + '@scalar/use-toasts': 0.7.4(typescript@5.5.4) + '@scalar/use-tooltip': 1.0.1(typescript@5.5.4) + '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.5.4)) axios: 1.7.2 httpsnippet-lite: 3.0.5 nanoid: 5.0.7 pretty-bytes: 6.1.1 pretty-ms: 8.0.0 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) transitivePeerDependencies: - '@jest/globals' - '@types/bun' @@ -11841,21 +11834,21 @@ snapshots: - typescript - vitest - '@scalar/api-reference@1.24.20(postcss@8.4.40)(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': + '@scalar/api-reference@1.24.20(postcss@8.4.40)(tailwindcss@3.4.3)(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': dependencies: - '@headlessui/vue': 1.7.22(vue@3.4.30(typescript@5.4.5)) - '@scalar/api-client': 1.3.19(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) - '@scalar/api-client-modal': 0.0.16(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) - '@scalar/components': 0.12.4(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + '@headlessui/vue': 1.7.22(vue@3.4.30(typescript@5.5.4)) + '@scalar/api-client': 1.3.19(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + '@scalar/api-client-modal': 0.0.16(tailwindcss@3.4.3)(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + '@scalar/components': 0.12.4(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) '@scalar/oas-utils': 0.2.5 '@scalar/openapi-parser': 0.7.1 '@scalar/snippetz': 0.1.6 - '@scalar/themes': 0.9.8(typescript@5.4.5) - '@scalar/use-toasts': 0.7.4(typescript@5.4.5) - '@scalar/use-tooltip': 1.0.1(typescript@5.4.5) + '@scalar/themes': 0.9.8(typescript@5.5.4) + '@scalar/use-toasts': 0.7.4(typescript@5.5.4) + '@scalar/use-tooltip': 1.0.1(typescript@5.5.4) '@unhead/schema': 1.9.14 - '@unhead/vue': 1.9.14(vue@3.4.30(typescript@5.4.5)) - '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.4.5)) + '@unhead/vue': 1.9.14(vue@3.4.30(typescript@5.5.4)) + '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.5.4)) axios: 1.7.2 fuse.js: 7.0.0 github-slugger: 2.0.0 @@ -11863,7 +11856,7 @@ snapshots: postcss-nested: 6.0.1(postcss@8.4.40) unhead: 1.9.14 unified: 11.0.4 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) transitivePeerDependencies: - '@jest/globals' - '@types/bun' @@ -11877,26 +11870,26 @@ snapshots: - typescript - vitest - '@scalar/client-app@0.1.14(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': + '@scalar/client-app@0.1.14(tailwindcss@3.4.3)(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': dependencies: '@headlessui/tailwindcss': 0.2.1(tailwindcss@3.4.3) - '@headlessui/vue': 1.7.22(vue@3.4.30(typescript@5.4.5)) - '@scalar/components': 0.12.4(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) - '@scalar/draggable': 0.1.3(typescript@5.4.5) + '@headlessui/vue': 1.7.22(vue@3.4.30(typescript@5.5.4)) + '@scalar/components': 0.12.4(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + '@scalar/draggable': 0.1.3(typescript@5.5.4) '@scalar/oas-utils': 0.2.5 '@scalar/object-utils': 1.1.2 '@scalar/openapi-parser': 0.7.1 - '@scalar/use-toasts': 0.7.4(typescript@5.4.5) - '@scalar/use-tooltip': 1.0.1(typescript@5.4.5) - '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.4.5)) + '@scalar/use-toasts': 0.7.4(typescript@5.5.4) + '@scalar/use-tooltip': 1.0.1(typescript@5.5.4) + '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.5.4)) axios: 1.7.2 - cva: 1.0.0-beta.1(typescript@5.4.5) + cva: 1.0.0-beta.1(typescript@5.5.4) js-cookie: 3.0.5 nanoid: 5.0.7 pretty-bytes: 6.1.1 pretty-ms: 8.0.0 - vue: 3.4.30(typescript@5.4.5) - vue-router: 4.4.0(vue@3.4.30(typescript@5.4.5)) + vue: 3.4.30(typescript@5.5.4) + vue-router: 4.4.0(vue@3.4.30(typescript@5.5.4)) zod: 3.23.8 transitivePeerDependencies: - '@jest/globals' @@ -11932,20 +11925,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@scalar/components@0.12.4(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': + '@scalar/components@0.12.4(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': dependencies: '@floating-ui/utils': 0.2.2 - '@floating-ui/vue': 1.0.7(vue@3.4.30(typescript@5.4.5)) - '@headlessui/vue': 1.7.22(vue@3.4.30(typescript@5.4.5)) + '@floating-ui/vue': 1.0.7(vue@3.4.30(typescript@5.5.4)) + '@headlessui/vue': 1.7.22(vue@3.4.30(typescript@5.5.4)) '@scalar/code-highlight': 0.0.5 '@scalar/oas-utils': 0.2.5 '@storybook/test': 8.1.11(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) - '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.4.5)) - cva: 1.0.0-beta.1(typescript@5.4.5) + '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.5.4)) + cva: 1.0.0-beta.1(typescript@5.5.4) nanoid: 5.0.7 - radix-vue: 1.8.5(vue@3.4.30(typescript@5.4.5)) + radix-vue: 1.8.5(vue@3.4.30(typescript@5.5.4)) tailwind-merge: 2.4.0 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) transitivePeerDependencies: - '@jest/globals' - '@types/bun' @@ -11957,15 +11950,15 @@ snapshots: - typescript - vitest - '@scalar/draggable@0.1.3(typescript@5.4.5)': + '@scalar/draggable@0.1.3(typescript@5.5.4)': dependencies: - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) transitivePeerDependencies: - typescript - '@scalar/nextjs-api-reference@0.4.18(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(postcss@8.4.40)(react-dom@18.2.0(react@18.3.1))(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': + '@scalar/nextjs-api-reference@0.4.18(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(postcss@8.4.40)(react-dom@18.2.0(react@18.3.1))(tailwindcss@3.4.3)(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1))': dependencies: - '@scalar/api-reference': 1.24.20(postcss@8.4.40)(tailwindcss@3.4.3)(typescript@5.4.5)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) + '@scalar/api-reference': 1.24.20(postcss@8.4.40)(tailwindcss@3.4.3)(typescript@5.5.4)(vitest@1.6.0(@types/node@20.12.12)(terser@5.31.1)) next: 14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) react: 18.3.1 transitivePeerDependencies: @@ -12042,13 +12035,13 @@ snapshots: '@scalar/snippetz-plugin-node-ofetch': 0.1.1 '@scalar/snippetz-plugin-node-undici': 0.1.6 - '@scalar/themes@0.9.8(typescript@5.4.5)': + '@scalar/themes@0.9.8(typescript@5.5.4)': dependencies: - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) transitivePeerDependencies: - typescript - '@scalar/use-codemirror@0.11.4(typescript@5.4.5)': + '@scalar/use-codemirror@0.11.4(typescript@5.5.4)': dependencies: '@codemirror/autocomplete': 6.16.3(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.28.2)(@lezer/common@1.2.1) '@codemirror/commands': 6.6.0 @@ -12065,25 +12058,25 @@ snapshots: '@replit/codemirror-css-color-picker': 6.1.1(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.28.2) '@uiw/codemirror-themes': 4.22.2(@codemirror/language@6.10.2)(@codemirror/state@6.4.1)(@codemirror/view@6.28.2) codemirror: 6.0.1(@lezer/common@1.2.1) - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) optionalDependencies: y-codemirror.next: 0.3.5(@codemirror/state@6.4.1)(@codemirror/view@6.28.2)(yjs@13.6.15) yjs: 13.6.15 transitivePeerDependencies: - typescript - '@scalar/use-toasts@0.7.4(typescript@5.4.5)': + '@scalar/use-toasts@0.7.4(typescript@5.5.4)': dependencies: nanoid: 5.0.7 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) vue-sonner: 1.1.3 transitivePeerDependencies: - typescript - '@scalar/use-tooltip@1.0.1(typescript@5.4.5)': + '@scalar/use-tooltip@1.0.1(typescript@5.5.4)': dependencies: - radix-vue: 1.8.5(vue@3.4.30(typescript@5.4.5)) - vue: 3.4.30(typescript@5.4.5) + radix-vue: 1.8.5(vue@3.4.30(typescript@5.5.4)) + vue: 3.4.30(typescript@5.5.4) transitivePeerDependencies: - '@vue/composition-api' - typescript @@ -12800,18 +12793,18 @@ snapshots: dependencies: '@swc/counter': 0.1.3 - '@t3-oss/env-core@0.10.1(typescript@5.4.5)(zod@3.23.8)': + '@t3-oss/env-core@0.10.1(typescript@5.5.4)(zod@3.23.8)': dependencies: zod: 3.23.8 optionalDependencies: - typescript: 5.4.5 + typescript: 5.5.4 - '@t3-oss/env-nextjs@0.10.1(typescript@5.4.5)(zod@3.23.8)': + '@t3-oss/env-nextjs@0.10.1(typescript@5.5.4)(zod@3.23.8)': dependencies: - '@t3-oss/env-core': 0.10.1(typescript@5.4.5)(zod@3.23.8) + '@t3-oss/env-core': 0.10.1(typescript@5.5.4)(zod@3.23.8) zod: 3.23.8 optionalDependencies: - typescript: 5.4.5 + typescript: 5.5.4 '@tailwindcss/typography@0.5.13(tailwindcss@3.4.3)': dependencies: @@ -12821,15 +12814,12 @@ snapshots: postcss-selector-parser: 6.0.10 tailwindcss: 3.4.3 - '@tanstack/query-core@4.36.1': {} + '@tanstack/query-core@5.53.3': {} - '@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': + '@tanstack/react-query@5.53.3(react@18.3.1)': dependencies: - '@tanstack/query-core': 4.36.1 + '@tanstack/query-core': 5.53.3 react: 18.3.1 - use-sync-external-store: 1.2.2(react@18.3.1) - optionalDependencies: - react-dom: 18.2.0(react@18.3.1) '@tanstack/react-table@8.20.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': dependencies: @@ -12849,10 +12839,10 @@ snapshots: '@tanstack/virtual-core@3.7.0': {} - '@tanstack/vue-virtual@3.7.0(vue@3.4.30(typescript@5.4.5))': + '@tanstack/vue-virtual@3.7.0(vue@3.4.30(typescript@5.5.4))': dependencies: '@tanstack/virtual-core': 3.7.0 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) '@testing-library/dom@10.1.0': dependencies: @@ -13024,29 +13014,30 @@ snapshots: transitivePeerDependencies: - tailwindcss - '@trpc/client@10.45.2(@trpc/server@10.45.2)': + '@trpc/client@11.0.0-rc.498(@trpc/server@11.0.0-rc.498)': dependencies: - '@trpc/server': 10.45.2 + '@trpc/server': 11.0.0-rc.498 - '@trpc/next@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/server@10.45.2)(next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': + '@trpc/next@11.0.0-rc.498(@tanstack/react-query@5.53.3(react@18.3.1))(@trpc/client@11.0.0-rc.498(@trpc/server@11.0.0-rc.498))(@trpc/react-query@11.0.0-rc.498(@tanstack/react-query@5.53.3(react@18.3.1))(@trpc/client@11.0.0-rc.498(@trpc/server@11.0.0-rc.498))(@trpc/server@11.0.0-rc.498)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/server@11.0.0-rc.498)(next@14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': dependencies: - '@tanstack/react-query': 4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1) - '@trpc/client': 10.45.2(@trpc/server@10.45.2) - '@trpc/react-query': 10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) - '@trpc/server': 10.45.2 + '@trpc/client': 11.0.0-rc.498(@trpc/server@11.0.0-rc.498) + '@trpc/server': 11.0.0-rc.498 next: 14.2.4(@babel/core@7.24.5)(@opentelemetry/api@1.9.0)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) react: 18.3.1 react-dom: 18.2.0(react@18.3.1) + optionalDependencies: + '@tanstack/react-query': 5.53.3(react@18.3.1) + '@trpc/react-query': 11.0.0-rc.498(@tanstack/react-query@5.53.3(react@18.3.1))(@trpc/client@11.0.0-rc.498(@trpc/server@11.0.0-rc.498))(@trpc/server@11.0.0-rc.498)(react-dom@18.2.0(react@18.3.1))(react@18.3.1) - '@trpc/react-query@10.45.2(@tanstack/react-query@4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1))(@trpc/client@10.45.2(@trpc/server@10.45.2))(@trpc/server@10.45.2)(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': + '@trpc/react-query@11.0.0-rc.498(@tanstack/react-query@5.53.3(react@18.3.1))(@trpc/client@11.0.0-rc.498(@trpc/server@11.0.0-rc.498))(@trpc/server@11.0.0-rc.498)(react-dom@18.2.0(react@18.3.1))(react@18.3.1)': dependencies: - '@tanstack/react-query': 4.36.1(react-dom@18.2.0(react@18.3.1))(react@18.3.1) - '@trpc/client': 10.45.2(@trpc/server@10.45.2) - '@trpc/server': 10.45.2 + '@tanstack/react-query': 5.53.3(react@18.3.1) + '@trpc/client': 11.0.0-rc.498(@trpc/server@11.0.0-rc.498) + '@trpc/server': 11.0.0-rc.498 react: 18.3.1 react-dom: 18.2.0(react@18.3.1) - '@trpc/server@10.45.2': {} + '@trpc/server@11.0.0-rc.498': {} '@types/aria-query@5.0.4': {} @@ -13277,13 +13268,13 @@ snapshots: dependencies: '@unhead/schema': 1.9.14 - '@unhead/vue@1.9.14(vue@3.4.30(typescript@5.4.5))': + '@unhead/vue@1.9.14(vue@3.4.30(typescript@5.5.4))': dependencies: '@unhead/schema': 1.9.14 '@unhead/shared': 1.9.14 hookable: 5.5.3 unhead: 1.9.14 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) '@vitest/expect@1.6.0': dependencies: @@ -13362,29 +13353,29 @@ snapshots: '@vue/shared': 3.4.30 csstype: 3.1.3 - '@vue/server-renderer@3.4.30(vue@3.4.30(typescript@5.4.5))': + '@vue/server-renderer@3.4.30(vue@3.4.30(typescript@5.5.4))': dependencies: '@vue/compiler-ssr': 3.4.30 '@vue/shared': 3.4.30 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) '@vue/shared@3.4.30': {} - '@vueuse/core@10.11.0(vue@3.4.30(typescript@5.4.5))': + '@vueuse/core@10.11.0(vue@3.4.30(typescript@5.5.4))': dependencies: '@types/web-bluetooth': 0.0.20 '@vueuse/metadata': 10.11.0 - '@vueuse/shared': 10.11.0(vue@3.4.30(typescript@5.4.5)) - vue-demi: 0.14.8(vue@3.4.30(typescript@5.4.5)) + '@vueuse/shared': 10.11.0(vue@3.4.30(typescript@5.5.4)) + vue-demi: 0.14.8(vue@3.4.30(typescript@5.5.4)) transitivePeerDependencies: - '@vue/composition-api' - vue '@vueuse/metadata@10.11.0': {} - '@vueuse/shared@10.11.0(vue@3.4.30(typescript@5.4.5))': + '@vueuse/shared@10.11.0(vue@3.4.30(typescript@5.5.4))': dependencies: - vue-demi: 0.14.8(vue@3.4.30(typescript@5.4.5)) + vue-demi: 0.14.8(vue@3.4.30(typescript@5.5.4)) transitivePeerDependencies: - '@vue/composition-api' - vue @@ -14454,11 +14445,11 @@ snapshots: csstype@3.1.3: {} - cva@1.0.0-beta.1(typescript@5.4.5): + cva@1.0.0-beta.1(typescript@5.5.4): dependencies: clsx: 2.0.0 optionalDependencies: - typescript: 5.4.5 + typescript: 5.5.4 d3-array@3.2.4: dependencies: @@ -15696,7 +15687,7 @@ snapshots: klona@2.0.6: {} - knip@5.17.4(@types/node@20.12.12)(typescript@5.4.5): + knip@5.17.4(@types/node@20.12.12)(typescript@5.5.4): dependencies: '@ericcornelissen/bash-parser': 0.5.2 '@nodelib/fs.walk': 2.0.0 @@ -15715,7 +15706,7 @@ snapshots: smol-toml: 1.1.4 strip-json-comments: 5.0.1 summary: 2.1.0 - typescript: 5.4.5 + typescript: 5.5.4 zod: 3.23.8 zod-validation-error: 3.3.0(zod@3.23.8) @@ -17097,12 +17088,12 @@ snapshots: dependencies: '@prisma/client': 5.14.0(prisma@5.14.0) - prisma-json-types-generator@3.0.4(prisma@5.14.0)(typescript@5.4.5): + prisma-json-types-generator@3.0.4(prisma@5.14.0)(typescript@5.5.4): dependencies: '@prisma/generator-helper': 5.9.1 prisma: 5.14.0 tslib: 2.6.2 - typescript: 5.4.5 + typescript: 5.5.4 prisma@5.14.0: dependencies: @@ -17266,20 +17257,20 @@ snapshots: quick-format-unescaped@4.0.4: {} - radix-vue@1.8.5(vue@3.4.30(typescript@5.4.5)): + radix-vue@1.8.5(vue@3.4.30(typescript@5.5.4)): dependencies: '@floating-ui/dom': 1.6.5 - '@floating-ui/vue': 1.0.7(vue@3.4.30(typescript@5.4.5)) + '@floating-ui/vue': 1.0.7(vue@3.4.30(typescript@5.5.4)) '@internationalized/date': 3.5.4 '@internationalized/number': 3.5.3 - '@tanstack/vue-virtual': 3.7.0(vue@3.4.30(typescript@5.4.5)) - '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.4.5)) - '@vueuse/shared': 10.11.0(vue@3.4.30(typescript@5.4.5)) + '@tanstack/vue-virtual': 3.7.0(vue@3.4.30(typescript@5.5.4)) + '@vueuse/core': 10.11.0(vue@3.4.30(typescript@5.5.4)) + '@vueuse/shared': 10.11.0(vue@3.4.30(typescript@5.5.4)) aria-hidden: 1.2.4 defu: 6.1.4 fast-deep-equal: 3.1.3 nanoid: 5.0.7 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) transitivePeerDependencies: - '@vue/composition-api' @@ -18321,7 +18312,7 @@ snapshots: typescript@5.1.6: {} - typescript@5.4.5: {} + typescript@5.5.4: {} ua-parser-js@1.0.37: {} @@ -18500,10 +18491,6 @@ snapshots: optionalDependencies: '@types/react': 18.3.3 - use-sync-external-store@1.2.2(react@18.3.1): - dependencies: - react: 18.3.1 - usehooks-ts@3.1.0(react@18.3.1): dependencies: lodash.debounce: 4.0.8 @@ -18650,26 +18637,26 @@ snapshots: vlq@0.2.3: {} - vue-demi@0.14.8(vue@3.4.30(typescript@5.4.5)): + vue-demi@0.14.8(vue@3.4.30(typescript@5.5.4)): dependencies: - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) - vue-router@4.4.0(vue@3.4.30(typescript@5.4.5)): + vue-router@4.4.0(vue@3.4.30(typescript@5.5.4)): dependencies: '@vue/devtools-api': 6.6.3 - vue: 3.4.30(typescript@5.4.5) + vue: 3.4.30(typescript@5.5.4) vue-sonner@1.1.3: {} - vue@3.4.30(typescript@5.4.5): + vue@3.4.30(typescript@5.5.4): dependencies: '@vue/compiler-dom': 3.4.30 '@vue/compiler-sfc': 3.4.30 '@vue/runtime-dom': 3.4.30 - '@vue/server-renderer': 3.4.30(vue@3.4.30(typescript@5.4.5)) + '@vue/server-renderer': 3.4.30(vue@3.4.30(typescript@5.5.4)) '@vue/shared': 3.4.30 optionalDependencies: - typescript: 5.4.5 + typescript: 5.5.4 w3c-keyname@2.2.8: {} From fcf2db64585b542c8fb537fe6c1682d5929b17f3 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Mon, 2 Sep 2024 21:41:25 +0530 Subject: [PATCH 28/34] feat: use new trpc --- .../(dashboard)/[publicId]/audits/page.tsx | 2 +- .../data-rooms/[dataRoomPublicId]/page.tsx | 4 +- .../esign/[templatePublicId]/page.tsx | 9 ++- .../[publicId]/documents/esign/page.tsx | 2 +- .../esign/v/[templatePublicId]/page.tsx | 4 +- .../(dashboard)/[publicId]/documents/page.tsx | 5 +- .../[publicId]/documents/share/_page.tsx | 2 +- .../[publicId]/fundraise/safes/page.tsx | 2 +- .../[publicId]/securities/options/page.tsx | 6 +- .../[publicId]/securities/shares/page.tsx | 6 +- .../settings/bank-accounts/page.tsx | 2 +- .../[publicId]/settings/billing/page.tsx | 4 +- .../[publicId]/settings/company/page.tsx | 5 +- .../[publicId]/settings/developer/page.tsx | 2 +- .../[publicId]/settings/profile/page.tsx | 4 +- .../[publicId]/settings/roles/page.tsx | 2 +- .../settings/security/passkey/page.tsx | 2 +- .../[publicId]/settings/team/page.tsx | 4 +- .../[publicId]/stakeholders/page.tsx | 2 +- .../(dashboard)/[publicId]/updates/page.tsx | 2 +- src/app/(documents)/esign/[token]/page.tsx | 2 +- .../dashboard/overview/activities-card.tsx | 2 +- .../safe/steps/investor-details/index.tsx | 2 +- .../securities/shares/share-modal.tsx | 4 +- src/trpc/api/trpc.ts | 32 ++++------ src/trpc/react.tsx | 60 ++++++++++-------- src/trpc/server.ts | 62 ++++--------------- src/trpc/shared.ts | 23 +++++++ 28 files changed, 117 insertions(+), 141 deletions(-) diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/audits/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/audits/page.tsx index 587c79371..f38cadb8c 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/audits/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/audits/page.tsx @@ -18,7 +18,7 @@ const AuditsPage = async () => { return ; } - const audits = await api.audit.getAudits.query({}); + const audits = await api.audit.getAudits({}); return (
diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/documents/data-rooms/[dataRoomPublicId]/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/documents/data-rooms/[dataRoomPublicId]/page.tsx index 958d421d8..195e80eb1 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/documents/data-rooms/[dataRoomPublicId]/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/documents/data-rooms/[dataRoomPublicId]/page.tsx @@ -10,7 +10,7 @@ const DataRoomSettinsPage = async ({ }: { params: { publicId: string; dataRoomPublicId: string }; }) => { - const { dataRoom, documents } = await api.dataRoom.getDataRoom.query({ + const { dataRoom, documents } = await api.dataRoom.getDataRoom({ dataRoomPublicId, include: { company: false, @@ -18,7 +18,7 @@ const DataRoomSettinsPage = async ({ documents: true, }, }); - const contacts = await api.common.getContacts.query(); + const contacts = await api.common.getContacts(); if (!dataRoom) { return notFound(); diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/documents/esign/[templatePublicId]/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/documents/esign/[templatePublicId]/page.tsx index 55fc8dbbc..772f80b05 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/documents/esign/[templatePublicId]/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/documents/esign/[templatePublicId]/page.tsx @@ -13,11 +13,10 @@ const EsignTemplateDetailPage = async ({ }) => { const session = await withServerComponentSession(); - const { name, status, url, fields, recipients } = - await api.template.get.query({ - publicId: templatePublicId, - isDraftOnly: true, - }); + const { name, status, url, fields, recipients } = await api.template.get({ + publicId: templatePublicId, + isDraftOnly: true, + }); return ( diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/documents/esign/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/documents/esign/page.tsx index 2a423f3a5..0f40e167e 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/documents/esign/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/documents/esign/page.tsx @@ -14,7 +14,7 @@ export const metadata: Metadata = { const EsignDocumentPage = async () => { const session = await withServerComponentSession(); - const { documents } = await api.template.all.query(); + const { documents } = await api.template.all(); if (documents.length === 0) { return ( diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/documents/esign/v/[templatePublicId]/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/documents/esign/v/[templatePublicId]/page.tsx index 648983984..957568a10 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/documents/esign/v/[templatePublicId]/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/documents/esign/v/[templatePublicId]/page.tsx @@ -22,13 +22,13 @@ export default async function TemplateDetailViewPage({ const { allow } = await serverAccessControl(); const [{ name, status, url, fields }, auditsData] = await Promise.all([ - api.template.get.query({ + api.template.get({ publicId: templatePublicId, isDraftOnly: false, }), allow( - api.audit.allEsignAudits.query({ + api.audit.allEsignAudits({ templatePublicId: templatePublicId, }), ["audits", "read"], diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/documents/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/documents/page.tsx index 1306b2bcc..e7386751f 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/documents/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/documents/page.tsx @@ -18,10 +18,7 @@ const DocumentsPage = async () => { const { allow } = await serverAccessControl(); const session = await withServerComponentSession(); - const documents = await allow(api.document.getAll.query(), [ - "documents", - "read", - ]); + const documents = await allow(api.document.getAll(), ["documents", "read"]); const canUpload = allow(true, ["documents", "read"]); diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/documents/share/_page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/documents/share/_page.tsx index fb635f74e..6f89f0d9c 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/documents/share/_page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/documents/share/_page.tsx @@ -14,7 +14,7 @@ export const metadata: Metadata = { }; const DocumentsPage = async () => { - const documents = await api.document.getAll.query(); + const documents = await api.document.getAll(); const session = await withServerComponentSession(); if (documents.length === 0) { diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/safes/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/safes/page.tsx index d81acf197..c864677f2 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/safes/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/fundraise/safes/page.tsx @@ -12,7 +12,7 @@ export const metadata: Metadata = { }; const SafePage = async () => { - const safes = await api.safe.getSafes.query(); + const safes = await api.safe.getSafes(); if (!safes?.data?.length) { return ( diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/securities/options/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/securities/options/page.tsx index 03e7cadcc..0ace13a45 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/securities/options/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/securities/options/page.tsx @@ -12,9 +12,9 @@ export const metadata: Metadata = { }; const OptionsPage = async () => { - const options = await api.securities.getOptions.query(); - const stakeholders = await api.stakeholder.getStakeholders.query(); - const equityPlans = await api.equityPlan.getPlans.query(); + const options = await api.securities.getOptions(); + const stakeholders = await api.stakeholder.getStakeholders(); + const equityPlans = await api.equityPlan.getPlans(); if (options?.data?.length === 0) { return ( diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/securities/shares/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/securities/shares/page.tsx index fbd24edb0..a8d9a25a8 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/securities/shares/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/securities/shares/page.tsx @@ -13,9 +13,9 @@ export const metadata: Metadata = { }; const SharesPage = async () => { - const shares = await api.securities.getShares.query(); - const stakeholders = await api.stakeholder.getStakeholders.query(); - const shareClasses = await api.shareClass.get.query(); + const shares = await api.securities.getShares(); + const stakeholders = await api.stakeholder.getStakeholders(); + const shareClasses = await api.shareClass.get(); if (shares?.data?.length === 0) { return ( diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/page.tsx index 357d32436..d6f24244e 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/bank-accounts/page.tsx @@ -14,7 +14,7 @@ export const metadata: Metadata = { const ApiSettingsPage = async () => { const { allow } = await serverAccessControl(); - const data = await allow(api.bankAccounts.getAll.query(), [ + const data = await allow(api.bankAccounts.getAll(), [ "bank-accounts", "read", ]); diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/billing/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/billing/page.tsx index 84937e1a7..2fd7a8331 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/billing/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/billing/page.tsx @@ -8,8 +8,8 @@ export const metadata: Metadata = { }; const BillingPage = async () => { const [{ products }, { subscription }] = await Promise.all([ - api.billing.getProducts.query(), - api.billing.getSubscription.query(), + api.billing.getProducts(), + api.billing.getSubscription(), ]); return ( diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/company/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/company/page.tsx index 4deb24116..f56eb5d8f 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/company/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/company/page.tsx @@ -10,10 +10,7 @@ export const metadata: Metadata = { const CompanySettingsPage = async () => { const { allow } = await serverAccessControl(); - const data = await allow(api.company.getCompany.query(), [ - "company", - "update", - ]); + const data = await allow(api.company.getCompany(), ["company", "update"]); if (!data?.company) { return ; diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/page.tsx index c5a338ce6..07e6c188c 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/developer/page.tsx @@ -11,7 +11,7 @@ export const metadata: Metadata = { title: "Developer settings", }; const AccessTokenPage = async () => { - const data = await api.accessToken.listAll.query({ + const data = await api.accessToken.listAll({ typeEnum: "api", }); diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/profile/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/profile/page.tsx index 4cc38559e..d9799c19c 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/profile/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/profile/page.tsx @@ -1,13 +1,13 @@ import { ProfileSettings } from "@/components/member/member-profile"; import { api } from "@/trpc/server"; -import { type Metadata } from "next"; +import type { Metadata } from "next"; export const metadata: Metadata = { title: "Profile", }; const ProfileSettingsPage = async () => { - const memberProfile = await api.member.getProfile.query(); + const memberProfile = await api.member.getProfile(); return ; }; diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/roles/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/roles/page.tsx index a46af7f32..3586b6659 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/roles/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/roles/page.tsx @@ -8,7 +8,7 @@ import { api } from "@/trpc/server"; export default async function RolesPage() { const { allow } = await serverAccessControl(); - const data = await allow(api.rbac.listRoles.query(), ["roles", "read"]); + const data = await allow(api.rbac.listRoles(), ["roles", "read"]); const canCreate = allow(true, ["roles", "create"]); diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/security/passkey/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/security/passkey/page.tsx index 7d4683a84..aaae7ac9c 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/security/passkey/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/security/passkey/page.tsx @@ -10,7 +10,7 @@ export const metadata: Metadata = { title: "Security", }; export default async function PasskeyPage() { - const passkeys = await api.passkey.find.query(); + const passkeys = await api.passkey.find(); return (
diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/settings/team/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/settings/team/page.tsx index a9b32339a..531299df5 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/settings/team/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/settings/team/page.tsx @@ -11,8 +11,8 @@ export const metadata: Metadata = { const TeamMembersPage = async () => { const [members, roles] = await Promise.all([ - api.member.getMembers.query(), - api.rbac.listRoles.query(), + api.member.getMembers(), + api.rbac.listRoles(), ]); return (
diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/stakeholders/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/stakeholders/page.tsx index fbf95c50e..5f545e569 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/stakeholders/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/stakeholders/page.tsx @@ -17,7 +17,7 @@ const StakeholdersPage = async () => { const session = await withServerSession(); const { allow } = await serverAccessControl(); - const stakeholders = await allow(api.stakeholder.getStakeholders.query(), [ + const stakeholders = await allow(api.stakeholder.getStakeholders(), [ "stakeholder", "read", ]); diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/updates/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/updates/page.tsx index 982e28c3e..2209dcc48 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/updates/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/updates/page.tsx @@ -16,7 +16,7 @@ const UpdatesPage = async ({ }: { params: { publicId: string }; }) => { - const updates = await api.update.get.query(); + const updates = await api.update.get(); if (updates.data.length === 0) { return ( diff --git a/src/app/(documents)/esign/[token]/page.tsx b/src/app/(documents)/esign/[token]/page.tsx index 6e0e1ed84..9ad376a43 100644 --- a/src/app/(documents)/esign/[token]/page.tsx +++ b/src/app/(documents)/esign/[token]/page.tsx @@ -26,7 +26,7 @@ export default async function SigningPage(props: SigningPageProps) { templateId, signableFields, status: templateStatus, - } = await api.template.getSigningFields.query({ + } = await api.template.getSigningFields({ token, }); diff --git a/src/components/dashboard/overview/activities-card.tsx b/src/components/dashboard/overview/activities-card.tsx index b9dd3b1f3..4c20c3451 100644 --- a/src/components/dashboard/overview/activities-card.tsx +++ b/src/components/dashboard/overview/activities-card.tsx @@ -16,7 +16,7 @@ type Props = { }; const ActivityCard = async ({ className, publicId }: Props) => { - const activity = await api.audit.getAudits.query({ take: 4 }); + const activity = await api.audit.getAudits({ take: 4 }); return ( diff --git a/src/components/safe/steps/investor-details/index.tsx b/src/components/safe/steps/investor-details/index.tsx index 2a952eb61..9f299ce1f 100644 --- a/src/components/safe/steps/investor-details/index.tsx +++ b/src/components/safe/steps/investor-details/index.tsx @@ -5,6 +5,6 @@ import { InvestorDetailsForm } from "./form"; export { type TFormSchema } from "./form"; export async function InvestorDetails() { - const stakeholders = await api.stakeholder.getStakeholders.query(); + const stakeholders = await api.stakeholder.getStakeholders(); return ; } diff --git a/src/components/securities/shares/share-modal.tsx b/src/components/securities/shares/share-modal.tsx index cfebd90d0..3605d220d 100644 --- a/src/components/securities/shares/share-modal.tsx +++ b/src/components/securities/shares/share-modal.tsx @@ -12,12 +12,12 @@ import { GeneralDetails } from "./steps/general-details"; import { RelevantDates } from "./steps/relevant-dates"; async function ContributionDetailsStep() { - const stakeholders = await api.stakeholder.getStakeholders.query(); + const stakeholders = await api.stakeholder.getStakeholders(); return ; } async function GeneralDetailsStep() { - const shareClasses = await api.shareClass.get.query(); + const shareClasses = await api.shareClass.get(); return ; } diff --git a/src/trpc/api/trpc.ts b/src/trpc/api/trpc.ts index 2bdfaec0b..55b0a9089 100644 --- a/src/trpc/api/trpc.ts +++ b/src/trpc/api/trpc.ts @@ -21,6 +21,7 @@ import { import { getServerAuthSession } from "@/server/auth"; import { db } from "@/server/db"; import * as Sentry from "@sentry/nextjs"; +import { cache } from "react"; interface Meta { policies: addPolicyOption; @@ -38,7 +39,7 @@ interface Meta { * * @see https://trpc.io/docs/server/context */ -export const createTRPCContext = async (opts: { headers: Headers }) => { +export const createTRPCContext = cache(async (opts: { headers: Headers }) => { const session = await getServerAuthSession(); return { @@ -48,12 +49,11 @@ export const createTRPCContext = async (opts: { headers: Headers }) => { userAgent: getUserAgent(opts.headers), ...opts, }; -}; +}); export type CreateTRPCContextType = Awaited< ReturnType >; - const withAuthTrpcContext = ({ session, ...rest }: CreateTRPCContextType) => { if (!session || !session.user) { throw new TRPCError({ code: "UNAUTHORIZED" }); @@ -126,22 +126,12 @@ export type withAccessControlTrpcContextType = ReturnType< * ZodErrors so that you get typesafety on the frontend if your procedure fails due to validation * errors on the backend. */ -const t = initTRPC - .context() - .meta() - .create({ - transformer: superjson, - errorFormatter({ shape, error }) { - return { - ...shape, - data: { - ...shape.data, - zodError: - error.cause instanceof ZodError ? error.cause.flatten() : null, - }, - }; - }, - }); +const t = initTRPC.context().meta().create({ + /** + * @see https://trpc.io/docs/server/data-transformers + */ + transformer: superjson, +}); /** * 3. ROUTER & PROCEDURE (THE IMPORTANT BIT) @@ -155,7 +145,6 @@ const t = initTRPC * * @see https://trpc.io/docs/router */ -export const createTRPCRouter = t.router; const sentryMiddleware = t.middleware( Sentry.trpcMiddleware({ @@ -213,3 +202,6 @@ export const withAccessControl = t.procedure.use( }); }), ); + +export const createTRPCRouter = t.router; +export const createCallerFactory = t.createCallerFactory; diff --git a/src/trpc/react.tsx b/src/trpc/react.tsx index cadb403ee..a6f516355 100644 --- a/src/trpc/react.tsx +++ b/src/trpc/react.tsx @@ -1,48 +1,56 @@ "use client"; -import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; -import { loggerLink, unstable_httpBatchStreamLink } from "@trpc/client"; +import type { QueryClient } from "@tanstack/react-query"; +import { QueryClientProvider } from "@tanstack/react-query"; +import { httpBatchLink } from "@trpc/client"; import { createTRPCReact } from "@trpc/react-query"; import { useState } from "react"; -import { type AppRouter } from "@/trpc/api/root"; -import { getUrl, transformer } from "./shared"; +import type { AppRouter } from "@/trpc/api/root"; + +import { getUrl, makeQueryClient, transformer } from "./shared"; export const api = createTRPCReact(); -export function TRPCReactProvider(props: { - children: React.ReactNode; - cookies: string; -}) { - const [queryClient] = useState(() => new QueryClient()); +let clientQueryClientSingleton: QueryClient; +function getQueryClient() { + if (typeof window === "undefined") { + // Server: always make a new query client + return makeQueryClient(); + } + // Browser: use singleton pattern to keep the same query client + // biome-ignore lint/suspicious/noAssignInExpressions: + return (clientQueryClientSingleton ??= makeQueryClient()); +} + +export function TRPCReactProvider( + props: Readonly<{ + children: React.ReactNode; + cookies: string; + }>, +) { + // NOTE: Avoid useState when initializing the query client if you don't + // have a suspense boundary between this and the code that may + // suspend because React will throw away the client on the initial + // render if it suspends and there is no boundary + const queryClient = getQueryClient(); const [trpcClient] = useState(() => api.createClient({ - transformer, links: [ - loggerLink({ - enabled: (op) => - process.env.NODE_ENV === "development" || - (op.direction === "down" && op.result instanceof Error), - }), - unstable_httpBatchStreamLink({ + httpBatchLink({ + transformer, url: getUrl(), - headers() { - return { - cookie: props.cookies, - "x-trpc-source": "react", - }; - }, }), ], }), ); return ( - - + + {props.children} - - + + ); } diff --git a/src/trpc/server.ts b/src/trpc/server.ts index 56418bd1e..5dd70c688 100644 --- a/src/trpc/server.ts +++ b/src/trpc/server.ts @@ -1,25 +1,13 @@ import "server-only"; -import { - TRPCClientError, - createTRPCProxyClient, - loggerLink, -} from "@trpc/client"; -import { callProcedure } from "@trpc/server"; -import { observable } from "@trpc/server/observable"; -import type { TRPCErrorResponse } from "@trpc/server/rpc"; -import { cookies } from "next/headers"; +import { createHydrationHelpers } from "@trpc/react-query/rsc"; import { cache } from "react"; -import { env } from "@/env"; -import { type AppRouter, appRouter } from "@/trpc/api/root"; -import { createTRPCContext } from "@/trpc/api/trpc"; -import { transformer } from "./shared"; +import { cookies } from "next/headers"; +import { appRouter } from "./api/root"; +import { createCallerFactory, createTRPCContext } from "./api/trpc"; +import { makeQueryClient } from "./shared"; -/** - * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when - * handling a tRPC call from a React Server Component. - */ const createContext = cache(() => { return createTRPCContext({ headers: new Headers({ @@ -29,37 +17,9 @@ const createContext = cache(() => { }); }); -export const api = createTRPCProxyClient({ - transformer, - links: [ - loggerLink({ - enabled: (op) => - env.LOGS || (op.direction === "down" && op.result instanceof Error), - }), - /** - * Custom RSC link that lets us invoke procedures without using http requests. Since Server - * Components always run on the server, we can just call the procedure as a function. - */ - () => - ({ op }) => - observable((observer) => { - createContext() - .then((ctx) => { - return callProcedure({ - procedures: appRouter._def.procedures, - path: op.path, - rawInput: op.input, - ctx, - type: op.type, - }); - }) - .then((data) => { - observer.next({ result: { data } }); - observer.complete(); - }) - .catch((cause: TRPCErrorResponse) => { - observer.error(TRPCClientError.from(cause)); - }); - }), - ], -}); +export const getQueryClient = cache(makeQueryClient); +const caller = createCallerFactory(appRouter)(createContext); + +export const { trpc: api, HydrateClient } = createHydrationHelpers< + typeof appRouter +>(caller, getQueryClient); diff --git a/src/trpc/shared.ts b/src/trpc/shared.ts index 3b2e54ada..49dbc3c9a 100644 --- a/src/trpc/shared.ts +++ b/src/trpc/shared.ts @@ -3,6 +3,10 @@ import superjson from "superjson"; import { env } from "@/env"; import type { AppRouter } from "@/trpc/api/root"; +import { + QueryClient, + defaultShouldDehydrateQuery, +} from "@tanstack/react-query"; export const transformer = superjson; @@ -29,3 +33,22 @@ export type RouterInputs = inferRouterInputs; * @example type HelloOutput = RouterOutputs['example']['hello'] */ export type RouterOutputs = inferRouterOutputs; + +export function makeQueryClient() { + return new QueryClient({ + defaultOptions: { + queries: { + staleTime: 30 * 1000, + }, + dehydrate: { + serializeData: transformer.serialize, + shouldDehydrateQuery: (query) => + defaultShouldDehydrateQuery(query) || + query.state.status === "pending", + }, + hydrate: { + deserializeData: transformer.deserialize, + }, + }, + }); +} From 9ca66a620625f1aadd562ff5c9ed9d685086ffb7 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Mon, 2 Sep 2024 22:43:16 +0530 Subject: [PATCH 29/34] feat: add error boundary --- package.json | 1 + pnpm-lock.yaml | 13 +++++ .../[publicId]/stakeholders/page.tsx | 40 +++++----------- .../stakeholder/stakeholder-table.tsx | 48 ++++++++++++------- 4 files changed, 58 insertions(+), 44 deletions(-) diff --git a/package.json b/package.json index 01f7b655f..d82816c08 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "react-dom": "18.2.0", "react-dropzone": "^14.2.3", "react-email": "2.1.6", + "react-error-boundary": "^4.0.13", "react-hook-form": "^7.52.1", "react-number-format": "^5.3.4", "react-pdf": "^8.0.2", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 97ecf9eac..b1599e973 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -239,6 +239,9 @@ importers: react-email: specifier: 2.1.6 version: 2.1.6(@opentelemetry/api@1.9.0)(@swc/helpers@0.5.11)(eslint@9.8.0) + react-error-boundary: + specifier: ^4.0.13 + version: 4.0.13(react@18.3.1) react-hook-form: specifier: ^7.52.1 version: 7.52.1(react@18.3.1) @@ -7171,6 +7174,11 @@ packages: engines: {node: '>=18.0.0'} hasBin: true + react-error-boundary@4.0.13: + resolution: {integrity: sha512-b6PwbdSv8XeOSYvjt8LpgpKrZ0yGdtZokYwkwV2wlcZbxgopHX/hgPl5VgpnoVOWd868n1hktM8Qm4b+02MiLQ==} + peerDependencies: + react: '>=16.13.1' + react-hook-form@7.52.1: resolution: {integrity: sha512-uNKIhaoICJ5KQALYZ4TOaOLElyM+xipord+Ha3crEFhTntdLvWZqVY49Wqd/0GiVCA/f9NjemLeiNPjG7Hpurg==} engines: {node: '>=12.22.0'} @@ -17353,6 +17361,11 @@ snapshots: - utf-8-validate - webpack-cli + react-error-boundary@4.0.13(react@18.3.1): + dependencies: + '@babel/runtime': 7.24.5 + react: 18.3.1 + react-hook-form@7.52.1(react@18.3.1): dependencies: react: 18.3.1 diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/stakeholders/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/stakeholders/page.tsx index 5f545e569..0f1b776e6 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/stakeholders/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/stakeholders/page.tsx @@ -1,26 +1,25 @@ -import EmptyState from "@/components/common/empty-state"; import StakeholderDropdown from "@/components/stakeholder/stakeholder-dropdown"; import StakeholderTable from "@/components/stakeholder/stakeholder-table"; -import { Card } from "@/components/ui/card"; import { UnAuthorizedState } from "@/components/ui/un-authorized-state"; import { serverAccessControl } from "@/lib/rbac/access-control"; import { withServerSession } from "@/server/auth"; -import { api } from "@/trpc/server"; -import { RiGroup2Fill } from "@remixicon/react"; import type { Metadata } from "next"; +import { Suspense } from "react"; +import { ErrorBoundary } from "react-error-boundary"; export const metadata: Metadata = { title: "Stakeholders", }; -const StakeholdersPage = async () => { +const StakeholdersPage = async ({ + searchParams, +}: { + searchParams: Record; +}) => { const session = await withServerSession(); const { allow } = await serverAccessControl(); - const stakeholders = await allow(api.stakeholder.getStakeholders(), [ - "stakeholder", - "read", - ]); + const stakeholders = allow(true, ["stakeholder", "read"]); const stakeholderDropdown = allow( , @@ -32,18 +31,6 @@ const StakeholdersPage = async () => { return ; } - if (stakeholders.length === 0) { - return ( - } - title="You do not have any stakeholders!" - subtitle="Please click the button below to add or import stakeholders." - > - {stakeholderDropdown} - - ); - } - return (
@@ -57,12 +44,11 @@ const StakeholdersPage = async () => {
{stakeholderDropdown}
- - - + Something went wrong
}> + + + +
); }; diff --git a/src/components/stakeholder/stakeholder-table.tsx b/src/components/stakeholder/stakeholder-table.tsx index a6d85822b..f80e1e9e5 100644 --- a/src/components/stakeholder/stakeholder-table.tsx +++ b/src/components/stakeholder/stakeholder-table.tsx @@ -2,7 +2,6 @@ import { pushModal } from "@/components/modals"; import { Badge } from "@/components/ui/badge"; -import { Checkbox } from "@/components/ui/checkbox"; import { DataTable } from "@/components/ui/data-table/data-table"; import { DataTableBody } from "@/components/ui/data-table/data-table-body"; import { DataTableContent } from "@/components/ui/data-table/data-table-content"; @@ -17,13 +16,14 @@ import { import type { TGetManyStakeholderRes } from "@/server/api/client-handlers/stakeholder"; import { useManyStakeholder } from "@/server/api/client-hooks/stakeholder"; import { ManyStakeholderSortParams } from "@/server/api/schema/stakeholder"; -import type { RouterOutputs } from "@/trpc/shared"; -import { RiMore2Fill } from "@remixicon/react"; +import { RiGroup2Fill, RiMore2Fill } from "@remixicon/react"; import { createColumnHelper } from "@tanstack/react-table"; import { parseAsString } from "nuqs"; +import EmptyState from "../common/empty-state"; import { Allow } from "../rbac/allow"; import { Button } from "../ui/button"; +import { Card } from "../ui/card"; import { DropdownMenu, DropdownMenuContent, @@ -32,12 +32,10 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "../ui/dropdown-menu"; +import StakeholderDropdown from "./stakeholder-dropdown"; import { StakeholderTableToolbar } from "./stakeholder-table-toolbar"; -type Stakeholder = RouterOutputs["stakeholder"]["getStakeholders"]; - type StakeholderTableType = { - stakeholders: Stakeholder; companyId: string; }; @@ -200,7 +198,7 @@ const StakeholderTable = ({ companyId }: StakeholderTableType) => { const table = usePaginatedTable({ pageCount: data?.meta.pageCount ?? -1, columns, - data: data?.data ?? [], + data: data.data, state: { pagination, sorting, @@ -211,17 +209,33 @@ const StakeholderTable = ({ companyId }: StakeholderTableType) => { onColumnFiltersChange, }); + if (data?.data?.length === 0 && data.meta.totalCount === 0) { + return ( + } + title="You do not have any stakeholders!" + subtitle="Please click the button below to add or import stakeholders." + > + + + + + ); + } + return ( -
- - - - - - - - -
+ +
+ + + + + + + + +
+
); }; From 233b00464ffbba17acde464ef8537d56d35e5f7e Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Mon, 2 Sep 2024 23:26:52 +0530 Subject: [PATCH 30/34] chore: rename loading --- .../billing/pricing-modal/index.tsx | 4 ++-- .../onboarding/check-email/index.tsx | 21 ++++++++++--------- .../stakeholder/stakeholder-uploader.tsx | 2 +- 3 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/components/billing/pricing-modal/index.tsx b/src/components/billing/pricing-modal/index.tsx index ccb2a1fd4..311819487 100644 --- a/src/components/billing/pricing-modal/index.tsx +++ b/src/components/billing/pricing-modal/index.tsx @@ -44,7 +44,7 @@ function Plans({ products, subscription }: PricingProps) { const [billingInterval, setBillingInterval] = useState("month"); - const { mutateAsync: checkoutWithStripe, isLoading: checkoutLoading } = + const { mutateAsync: checkoutWithStripe, isPending: checkoutLoading } = api.billing.checkout.useMutation({ onSuccess: async ({ stripeSessionId }) => { const stripe = await getStripeClient(); @@ -52,7 +52,7 @@ function Plans({ products, subscription }: PricingProps) { }, }); - const { mutateAsync: stripePortal, isLoading: stripePortalLoading } = + const { mutateAsync: stripePortal, isPending: stripePortalLoading } = api.billing.stripePortal.useMutation({ onSuccess: ({ url }) => { router.push(url); diff --git a/src/components/onboarding/check-email/index.tsx b/src/components/onboarding/check-email/index.tsx index 0c25d5d7d..099fa63e0 100644 --- a/src/components/onboarding/check-email/index.tsx +++ b/src/components/onboarding/check-email/index.tsx @@ -10,16 +10,17 @@ const CheckEmailComponent = () => { const searchParams = useSearchParams(); const email = searchParams.get("email"); - const { mutateAsync, isLoading } = api.auth.resendEmail.useMutation({ - onSuccess: () => { - toast.success("🎉 Email successfully re-sent."); - }, - onError: () => { - toast.error( - "Uh oh! Something went wrong, please try again or contact support.", - ); - }, - }); + const { mutateAsync, isPending: isLoading } = + api.auth.resendEmail.useMutation({ + onSuccess: () => { + toast.success("🎉 Email successfully re-sent."); + }, + onError: () => { + toast.error( + "Uh oh! Something went wrong, please try again or contact support.", + ); + }, + }); async function Resend() { try { diff --git a/src/components/stakeholder/stakeholder-uploader.tsx b/src/components/stakeholder/stakeholder-uploader.tsx index aafa4d023..d5194f78e 100644 --- a/src/components/stakeholder/stakeholder-uploader.tsx +++ b/src/components/stakeholder/stakeholder-uploader.tsx @@ -17,7 +17,7 @@ const StakeholderUploader = () => { const router = useRouter(); - const { mutateAsync, isLoading } = + const { mutateAsync, isPending: isLoading } = api.stakeholder.addStakeholders.useMutation({ onSuccess: ({ success, message }) => { if (success) { From 33ba09b64c0d0e0ebdafbea348766bbf40a5ba08 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Mon, 2 Sep 2024 23:27:09 +0530 Subject: [PATCH 31/34] feat: use useSuspense --- src/server/api/client-hooks/stakeholder.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/server/api/client-hooks/stakeholder.ts b/src/server/api/client-hooks/stakeholder.ts index e29de104b..72133324c 100644 --- a/src/server/api/client-hooks/stakeholder.ts +++ b/src/server/api/client-hooks/stakeholder.ts @@ -1,13 +1,13 @@ "use client"; -import { useQuery } from "@tanstack/react-query"; +import { useSuspenseQuery } from "@tanstack/react-query"; import { type TGetManyStakeholderParams, getManyStakeholder, } from "../client-handlers/stakeholder"; export const useManyStakeholder = (data: TGetManyStakeholderParams) => - useQuery({ + useSuspenseQuery({ queryKey: [ "all-stakeholder", data.urlParams.companyId, From b91f92dd79c2ea824fa381e517fbe4b7207a8a2c Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Mon, 2 Sep 2024 23:46:03 +0530 Subject: [PATCH 32/34] fix: data --- .../stakeholder/stakeholder-table.tsx | 6 ++-- src/server/api/api-client.ts | 28 +++++-------------- 2 files changed, 10 insertions(+), 24 deletions(-) diff --git a/src/components/stakeholder/stakeholder-table.tsx b/src/components/stakeholder/stakeholder-table.tsx index f80e1e9e5..c6f37ecb4 100644 --- a/src/components/stakeholder/stakeholder-table.tsx +++ b/src/components/stakeholder/stakeholder-table.tsx @@ -196,9 +196,9 @@ const StakeholderTable = ({ companyId }: StakeholderTableType) => { }); const table = usePaginatedTable({ - pageCount: data?.meta.pageCount ?? -1, + pageCount: data?.meta?.pageCount ?? -1, columns, - data: data.data, + data: data?.data ?? [], state: { pagination, sorting, @@ -209,7 +209,7 @@ const StakeholderTable = ({ companyId }: StakeholderTableType) => { onColumnFiltersChange, }); - if (data?.data?.length === 0 && data.meta.totalCount === 0) { + if (data && data?.data?.length === 0 && data?.meta?.totalCount === 0) { return ( } diff --git a/src/server/api/api-client.ts b/src/server/api/api-client.ts index fd7f77853..7293ce3db 100644 --- a/src/server/api/api-client.ts +++ b/src/server/api/api-client.ts @@ -52,27 +52,13 @@ export async function createClient( url: U["path"], params: APIClientParams, ) { - try { - const path = buildPath(url, params); - const headers = buildHeaders(params.headers); - const requestOptions = buildRequestOptions(method, headers, params); - - const response = await fetch(path, requestOptions); - - if (!response.ok) { - const errorMessage = await getErrorMessage(response); - throw new Error(`HTTP Error: ${response.status} - ${errorMessage}`); - } - - try { - return response.json() as RouteConfigToTypedResponse["_data"]; - } catch (_jsonError) { - throw new Error("Failed to parse JSON response."); - } - } catch (error) { - console.error("Error in createClient:", error); - throw error; - } + const path = buildPath(url, params); + const headers = buildHeaders(params.headers); + const requestOptions = buildRequestOptions(method, headers, params); + + const response = await fetch(path, requestOptions); + + return response.json() as RouteConfigToTypedResponse["_data"]; } function interpolatePath( From c477f151fff2f209c1803d5114b30adb46c69c9a Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Tue, 17 Sep 2024 20:32:07 +0530 Subject: [PATCH 33/34] feat: add search param hook --- src/hooks/use-update-search-params.ts | 28 +++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/hooks/use-update-search-params.ts diff --git a/src/hooks/use-update-search-params.ts b/src/hooks/use-update-search-params.ts new file mode 100644 index 000000000..4710f0c02 --- /dev/null +++ b/src/hooks/use-update-search-params.ts @@ -0,0 +1,28 @@ +"use client"; + +import { usePathname, useRouter, useSearchParams } from "next/navigation"; + +export const useUpdateSearchParams = () => { + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + + return ( + params: Record, + ) => { + const nextSearchParams = new URLSearchParams( + searchParams?.toString() ?? "", + ); + + // biome-ignore lint/complexity/noForEach: + Object.entries(params).forEach(([key, value]) => { + if (value === undefined || value === null) { + nextSearchParams.delete(key); + } else { + nextSearchParams.set(key, String(value)); + } + }); + + router.push(`${pathname}?${nextSearchParams.toString()}`); + }; +}; From 160cf3d5ea9c5fe4e42b4ceb89482281d13eb8d1 Mon Sep 17 00:00:00 2001 From: nafees nazik Date: Tue, 17 Sep 2024 23:27:20 +0530 Subject: [PATCH 34/34] feat: add search --- .../[publicId]/stakeholders/page.tsx | 50 ++++- .../stakeholder/stakeholder-table-toolbar.tsx | 4 +- .../stakeholder/stakeholder-table.tsx | 78 ++----- src/hooks/use-paginated-data-table.ts | 212 +++++++----------- src/server/api/client-hooks/stakeholder.ts | 20 -- src/server/api/routes/stakeholder/getMany.ts | 9 +- src/server/api/schema/stakeholder.ts | 6 +- 7 files changed, 159 insertions(+), 220 deletions(-) delete mode 100644 src/server/api/client-hooks/stakeholder.ts diff --git a/src/app/(authenticated)/(dashboard)/[publicId]/stakeholders/page.tsx b/src/app/(authenticated)/(dashboard)/[publicId]/stakeholders/page.tsx index 0f1b776e6..2709fdee4 100644 --- a/src/app/(authenticated)/(dashboard)/[publicId]/stakeholders/page.tsx +++ b/src/app/(authenticated)/(dashboard)/[publicId]/stakeholders/page.tsx @@ -1,11 +1,14 @@ +import EmptyState from "@/components/common/empty-state"; import StakeholderDropdown from "@/components/stakeholder/stakeholder-dropdown"; import StakeholderTable from "@/components/stakeholder/stakeholder-table"; import { UnAuthorizedState } from "@/components/ui/un-authorized-state"; import { serverAccessControl } from "@/lib/rbac/access-control"; +import { getManyStakeholder } from "@/server/api/client-handlers/stakeholder"; +import { ManyStakeholderQuerySchema } from "@/server/api/schema/stakeholder"; import { withServerSession } from "@/server/auth"; +import { RiGroup2Fill } from "@remixicon/react"; import type { Metadata } from "next"; -import { Suspense } from "react"; -import { ErrorBoundary } from "react-error-boundary"; +import { headers } from "next/headers"; export const metadata: Metadata = { title: "Stakeholders", @@ -19,7 +22,20 @@ const StakeholdersPage = async ({ const session = await withServerSession(); const { allow } = await serverAccessControl(); - const stakeholders = allow(true, ["stakeholder", "read"]); + + const { limit, page, sort, name } = + ManyStakeholderQuerySchema.parse(searchParams); + + const stakeholders = await allow( + getManyStakeholder({ + searchParams: { limit, page, sort, ...(name && { name }) }, + urlParams: { + companyId: session.user.companyId, + }, + headers: headers(), + }), + ["stakeholder", "read"], + ); const stakeholderDropdown = allow( , @@ -31,6 +47,22 @@ const StakeholdersPage = async ({ return ; } + if ( + stakeholders?.data && + stakeholders?.data.length === 0 && + Object.keys(searchParams).length === 0 + ) { + return ( + } + title="You do not have any stakeholders!" + subtitle="Please click the button below to add or import stakeholders." + > + {stakeholderDropdown} + + ); + } + return (
@@ -44,11 +76,13 @@ const StakeholdersPage = async ({
{stakeholderDropdown}
- Something went wrong
}> - - - - +
); }; diff --git a/src/components/stakeholder/stakeholder-table-toolbar.tsx b/src/components/stakeholder/stakeholder-table-toolbar.tsx index e2e5c48a8..38a1b100f 100644 --- a/src/components/stakeholder/stakeholder-table-toolbar.tsx +++ b/src/components/stakeholder/stakeholder-table-toolbar.tsx @@ -1,3 +1,5 @@ +"use client"; + import { useTable } from "@/components/ui/data-table/data-table"; import { ResetButton } from "@/components/ui/data-table/data-table-buttons"; import { DataTableViewOptions } from "@/components/ui/data-table/data-table-view-options"; @@ -12,7 +14,7 @@ export const StakeholderTableToolbar = () => {
table.getColumn("name")?.setFilterValue(value)} className="h-8 w-64" diff --git a/src/components/stakeholder/stakeholder-table.tsx b/src/components/stakeholder/stakeholder-table.tsx index c6f37ecb4..05686a01d 100644 --- a/src/components/stakeholder/stakeholder-table.tsx +++ b/src/components/stakeholder/stakeholder-table.tsx @@ -7,20 +7,12 @@ import { DataTableBody } from "@/components/ui/data-table/data-table-body"; import { DataTableContent } from "@/components/ui/data-table/data-table-content"; import { DataTableHeader } from "@/components/ui/data-table/data-table-header"; import { DataTablePagination } from "@/components/ui/data-table/data-table-pagination"; -import { - useFilterQueryParams, - usePaginatedQueryParams, - usePaginatedTable, - useSortQueryParams, -} from "@/hooks/use-paginated-data-table"; +import { usePaginatedTable } from "@/hooks/use-paginated-data-table"; import type { TGetManyStakeholderRes } from "@/server/api/client-handlers/stakeholder"; -import { useManyStakeholder } from "@/server/api/client-hooks/stakeholder"; -import { ManyStakeholderSortParams } from "@/server/api/schema/stakeholder"; -import { RiGroup2Fill, RiMore2Fill } from "@remixicon/react"; +import type { TManyStakeholderQuerySchema } from "@/server/api/schema/stakeholder"; +import { RiMore2Fill } from "@remixicon/react"; import { createColumnHelper } from "@tanstack/react-table"; -import { parseAsString } from "nuqs"; -import EmptyState from "../common/empty-state"; import { Allow } from "../rbac/allow"; import { Button } from "../ui/button"; import { Card } from "../ui/card"; @@ -32,12 +24,11 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from "../ui/dropdown-menu"; -import StakeholderDropdown from "./stakeholder-dropdown"; import { StakeholderTableToolbar } from "./stakeholder-table-toolbar"; type StakeholderTableType = { - companyId: string; -}; + stakeholders: TGetManyStakeholderRes; +} & TManyStakeholderQuerySchema; const getStakeholderType = (type: string) => { switch (type) { @@ -173,56 +164,23 @@ const columns = [ }), ]; -const StakeholderTable = ({ companyId }: StakeholderTableType) => { - const { limit, page, onPaginationChange, pagination } = - usePaginatedQueryParams(); - - const { onSortingChange, sorting, sort } = useSortQueryParams( - ManyStakeholderSortParams, - "createdAt.desc", - ); - const { columnFilters, onColumnFiltersChange, state } = useFilterQueryParams({ - name: parseAsString, - }); - - const { data } = useManyStakeholder({ - searchParams: { - limit, - page, - sort, - ...(state.name && { name: state.name }), - }, - urlParams: { companyId }, - }); - +const StakeholderTable = ({ + stakeholders, + page, + limit, + sort, + name, +}: StakeholderTableType) => { const table = usePaginatedTable({ - pageCount: data?.meta?.pageCount ?? -1, + pageCount: stakeholders?.meta?.pageCount ?? -1, columns, - data: data?.data ?? [], - state: { - pagination, - sorting, - columnFilters, - }, - onPaginationChange, - onSortingChange, - onColumnFiltersChange, + data: stakeholders?.data ?? [], + limit, + page, + sort, + filterFields: [{ id: "name", value: name }], }); - if (data && data?.data?.length === 0 && data?.meta?.totalCount === 0) { - return ( - } - title="You do not have any stakeholders!" - subtitle="Please click the button below to add or import stakeholders." - > - - - - - ); - } - return (
diff --git a/src/hooks/use-paginated-data-table.ts b/src/hooks/use-paginated-data-table.ts index 6e90af657..b9dc01b57 100644 --- a/src/hooks/use-paginated-data-table.ts +++ b/src/hooks/use-paginated-data-table.ts @@ -1,55 +1,15 @@ +"use client"; + import type { - ColumnFilter, ColumnFiltersState, - PaginationState, RowData, SortingState, - TableState, Updater, } from "@tanstack/react-table"; -import { - type UseQueryStatesKeysMap, - type Values, - parseAsInteger, - parseAsStringLiteral, - useQueryState, - useQueryStates, -} from "nuqs"; -import { useMemo } from "react"; -import { type TDataTableOptions, useDataTable } from "./use-data-table"; - -type MakeRequired = Omit & Required>; - -type TState = MakeRequired, "pagination">; -export function usePaginatedQueryParams() { - const [{ limit, page }, setParams] = useQueryStates({ - page: parseAsInteger.withDefault(1), - limit: parseAsInteger.withDefault(10), - }); - - const pageIndex = page - 1; - const pageSize = limit; - - const onPaginationChange = (updater: Updater) => { - if (typeof updater === "function") { - const updateValue = updater({ pageIndex, pageSize }); - - setParams({ - limit: updateValue.pageSize, - page: updateValue.pageIndex + 1, - }); - } - }; - - return { - pagination: { pageIndex, pageSize }, - onPaginationChange, - limit, - page, - setParams, - }; -} +import { useRouter } from "next/navigation"; +import { type TDataTableOptions, useDataTable } from "./use-data-table"; +import { useUpdateSearchParams } from "./use-update-search-params"; function parseSortingState(sort: string) { const [field, direction] = sort.split("."); @@ -59,44 +19,15 @@ function parseSortingState(sort: string) { return state; } -export function useSortQueryParams< - T extends readonly string[], - U extends T[number], - V extends T[number], ->(schema: T, defaultValue: U) { - const [sort, setSort] = useQueryState( - "sort", - //@ts-expect-error - parseAsStringLiteral(schema).withDefault(defaultValue), - ); - - const sorting = parseSortingState(sort); - - const onSortingChange = (updater: Updater) => { - if (typeof updater === "function") { - const updateValue = updater(sorting); - - const sortValue = updateValue[0] - ? `${updateValue[0]?.id}.${updateValue[0]?.desc ? "desc" : "asc"}` - : defaultValue; - - setSort(sortValue as V); - } - }; - - return { onSortingChange, setSort, sort, sorting }; -} - -function parseFilteringState( - filter: Values, +function parseFilteringState( + filterFields?: { id: string; value: string | undefined }[], ) { - const columnFilters: ColumnFilter[] = []; + const columnFilters: ColumnFiltersState = []; - for (const key in filter) { - if (Object.prototype.hasOwnProperty.call(filter, key)) { - const value = filter[key]; - if (value) { - columnFilters.push({ id: key, value }); + if (filterFields) { + for (const filter of filterFields) { + if (filter.value) { + columnFilters.push({ id: filter.id, value: [filter.value] }); } } } @@ -104,62 +35,87 @@ function parseFilteringState( return columnFilters; } -function keyMapInitial(keyMap: KeyMap) { - return Object.keys(keyMap).reduce( - (obj, key) => { - // biome-ignore lint/style/noNonNullAssertion: - const { defaultValue } = keyMap[key]!; +export function usePaginatedTable({ + page, + limit, + sort, + filterFields = [], + ...options +}: TDataTableOptions & { + page: number; + limit: number; + sort: string; + filterFields?: { id: string; value: string | undefined }[]; +}) { + const router = useRouter(); + + const updateSearchParams = useUpdateSearchParams(); - obj[key as keyof KeyMap] = defaultValue ?? null; - return obj; - }, - {} as Values, - ); -} + const pageIndex = page - 1; + const pageSize = limit; + const sorting = parseSortingState(sort); -function toQueryState( - updateValue: ColumnFiltersState, -) { - return updateValue.reduce>((prev, current) => { - prev[current.id] = current.value; - return prev; - }, {}) as Values; -} + const columnFilters = parseFilteringState(filterFields); -export function useFilterQueryParams( - keyMap: KeyMap, -) { - const [state, setState] = useQueryStates(keyMap); + return useDataTable({ + manualPagination: true, + manualSorting: true, + manualFiltering: true, + pageCount: options.pageCount ?? -1, + state: { + pagination: { + pageIndex, + pageSize, + ...(options?.state?.pagination && { ...options.state.pagination }), + }, + sorting, + columnFilters, + ...(options?.state && { ...options.state }), + }, + onPaginationChange: (updater) => { + if (typeof updater === "function") { + const updateValue = updater({ pageIndex, pageSize }); + updateSearchParams({ + limit: updateValue.pageSize, + page: updateValue.pageIndex + 1, + }); + } + }, - const columnFilters = parseFilteringState(state); + onSortingChange: (updater: Updater) => { + if (typeof updater === "function") { + const updateValue = updater(sorting); - // biome-ignore lint/correctness/useExhaustiveDependencies: - const defaultValue = useMemo(() => { - return keyMapInitial(keyMap); - }, []); + const sortValue = updateValue[0] + ? `${updateValue[0]?.id}.${updateValue[0]?.desc ? "desc" : "asc"}` + : undefined; - const onColumnFiltersChange = (updater: Updater) => { - if (typeof updater === "function") { - const updateValue = updater(columnFilters); + updateSearchParams({ sort: sortValue }); + } + }, - if (updateValue.length) { - setState(toQueryState(updateValue)); - } else { - setState(defaultValue); + onColumnFiltersChange: (updater: Updater) => { + if (typeof updater === "function") { + const updateValue = updater(columnFilters); + if (updateValue.length) { + const state = updateValue.reduce>( + (prev, curr) => { + prev[curr.id] = curr.value; + + return prev; + }, + {}, + ); + + //@ts-expect-error + updateSearchParams({ ...state }); + } else { + const path = window.location.pathname; + router.push(path); + } } - } - }; - return { columnFilters, onColumnFiltersChange, state }; -} + }, -export function usePaginatedTable( - options: Omit, "state"> & { state: TState }, -) { - return useDataTable({ - manualPagination: true, - manualSorting: true, - manualFiltering: true, - pageCount: options.pageCount ?? -1, ...options, }); } diff --git a/src/server/api/client-hooks/stakeholder.ts b/src/server/api/client-hooks/stakeholder.ts deleted file mode 100644 index 72133324c..000000000 --- a/src/server/api/client-hooks/stakeholder.ts +++ /dev/null @@ -1,20 +0,0 @@ -"use client"; - -import { useSuspenseQuery } from "@tanstack/react-query"; -import { - type TGetManyStakeholderParams, - getManyStakeholder, -} from "../client-handlers/stakeholder"; - -export const useManyStakeholder = (data: TGetManyStakeholderParams) => - useSuspenseQuery({ - queryKey: [ - "all-stakeholder", - data.urlParams.companyId, - String(data.searchParams.limit), - String(data.searchParams.page), - String(data.searchParams.sort), - String(data.searchParams?.name ?? ""), - ], - queryFn: () => getManyStakeholder(data), - }); diff --git a/src/server/api/routes/stakeholder/getMany.ts b/src/server/api/routes/stakeholder/getMany.ts index 6ef74990b..5c437d955 100644 --- a/src/server/api/routes/stakeholder/getMany.ts +++ b/src/server/api/routes/stakeholder/getMany.ts @@ -51,13 +51,18 @@ export const getMany = withAuthApiV1 .handler(async (c) => { const { membership } = c.get("session"); const { db } = c.get("services"); - const { limit, page, sort, name } = c.req.valid("query"); + const { limit, page, sort, name: query } = c.req.valid("query"); const [data, meta] = await db.stakeholder .paginate({ where: { companyId: membership.companyId, - ...(name && { name: { contains: name, mode: "insensitive" } }), + ...(query && { + OR: [ + { name: { contains: query, mode: "insensitive" } }, + { email: { contains: query, mode: "insensitive" } }, + ], + }), }, orderBy: parseManyStakeholderSortParam(sort), }) diff --git a/src/server/api/schema/stakeholder.ts b/src/server/api/schema/stakeholder.ts index f2e21154a..073f1fb4c 100644 --- a/src/server/api/schema/stakeholder.ts +++ b/src/server/api/schema/stakeholder.ts @@ -117,10 +117,11 @@ const sortFields = generateSortParam(["createdAt", "name", "email"] as const); export const parseManyStakeholderSortParam = sortFields.parseSortParam; export const ManyStakeholderSortParams = sortFields.sortParams; +export const ManyStakeholderSortParamsSchema = sortFields.schema; export const ManyStakeholderQuerySchema = z .object({ - sort: sortFields.schema.optional().default("createdAt.desc"), + sort: ManyStakeholderSortParamsSchema.optional().default("createdAt.desc"), name: z.string().optional(), }) .merge(OffsetPaginationQuerySchema); @@ -130,3 +131,6 @@ export type TCreateStakeholderSchema = z.infer; export type TUpdateStakeholderSchema = z.infer; export type TManyStakeholderSortParams = (typeof ManyStakeholderSortParams)[number]; +export type TManyStakeholderQuerySchema = z.infer< + typeof ManyStakeholderQuerySchema +>;