From 217eb5bbb55c03b08b0c5c523e60f90ef843150a Mon Sep 17 00:00:00 2001 From: Eric Luce <37158449+eluce2@users.noreply.github.com> Date: Tue, 11 Mar 2025 11:25:05 -0500 Subject: [PATCH 1/2] new template --- .changeset/great-cycles-roll.md | 5 + .changeset/ninety-ligers-fly.md | 5 + cli/src/cli/add/auth.ts | 2 +- cli/src/cli/add/fmschema.ts | 6 +- cli/src/generators/auth.ts | 8 +- cli/src/installers/install-fm-addon.ts | 14 +- cli/src/installers/proofkit-auth.ts | 34 ++++- cli/template/extras/config/_eslint.js | 1 + .../nextjs/table-infinite-edit/actions.ts | 84 +++++++++++ .../pages/nextjs/table-infinite-edit/page.tsx | 23 ++++ .../pages/nextjs/table-infinite-edit/query.ts | 81 +++++++++++ .../nextjs/table-infinite-edit/schema.ts | 4 + .../nextjs/table-infinite-edit/table.tsx | 130 ++++++++++++++++++ .../pages/nextjs/table-infinite/actions.ts | 13 +- config/templates.ts | 7 + 15 files changed, 384 insertions(+), 33 deletions(-) create mode 100644 .changeset/great-cycles-roll.md create mode 100644 .changeset/ninety-ligers-fly.md create mode 100644 cli/template/pages/nextjs/table-infinite-edit/actions.ts create mode 100644 cli/template/pages/nextjs/table-infinite-edit/page.tsx create mode 100644 cli/template/pages/nextjs/table-infinite-edit/query.ts create mode 100644 cli/template/pages/nextjs/table-infinite-edit/schema.ts create mode 100644 cli/template/pages/nextjs/table-infinite-edit/table.tsx diff --git a/.changeset/great-cycles-roll.md b/.changeset/great-cycles-roll.md new file mode 100644 index 00000000..c0b8495d --- /dev/null +++ b/.changeset/great-cycles-roll.md @@ -0,0 +1,5 @@ +--- +"@proofgeist/kit": patch +--- + +Fixed infinite table queries for other field names diff --git a/.changeset/ninety-ligers-fly.md b/.changeset/ninety-ligers-fly.md new file mode 100644 index 00000000..0be09da1 --- /dev/null +++ b/.changeset/ninety-ligers-fly.md @@ -0,0 +1,5 @@ +--- +"@proofgeist/kit": patch +--- + +New infinite table editable template diff --git a/cli/src/cli/add/auth.ts b/cli/src/cli/add/auth.ts index 0f4b47b1..ad036bd7 100644 --- a/cli/src/cli/add/auth.ts +++ b/cli/src/cli/add/auth.ts @@ -48,7 +48,7 @@ export async function runAddAuthAction() { abortIfCancel( await p.select({ message: `What email provider do you want to use?\n${chalk.dim( - "Used to send email verification codes. If you skip this, the codes will be displayed in your terminal." + "Used to send email verification codes. If you skip this, the codes will be displayed here in your terminal." )}`, options: [ { diff --git a/cli/src/cli/add/fmschema.ts b/cli/src/cli/add/fmschema.ts index 9060946b..aea85136 100644 --- a/cli/src/cli/add/fmschema.ts +++ b/cli/src/cli/add/fmschema.ts @@ -84,9 +84,6 @@ export const runAddSchemaAction = async (opts?: { .map((s) => s.schemaName) .filter(Boolean); - // list other common layout names to exclude - existingLayouts.push("-"); - spinner.stop("Loaded layouts from your FileMaker file"); if (existingLayouts.length > 0) { @@ -96,6 +93,9 @@ export const runAddSchemaAction = async (opts?: { ); } + // list other common layout names to exclude + existingLayouts.push("-"); + let passedInLayoutName: string | undefined = opts?.layoutName; if ( passedInLayoutName === "" || diff --git a/cli/src/generators/auth.ts b/cli/src/generators/auth.ts index 3714b232..5abe04a6 100644 --- a/cli/src/generators/auth.ts +++ b/cli/src/generators/auth.ts @@ -33,7 +33,7 @@ export async function addAuth({ if (options.type === "clerk") { await addClerkAuth({ projectDir }); } else if (options.type === "fmaddon") { - await addFmaddonAuth(options); + await addFmaddonAuth(); } if (!noInstall) { @@ -50,11 +50,7 @@ async function addClerkAuth({ mergeSettings({ auth: { type: "clerk" } }); } -async function addFmaddonAuth({ - emailProvider, -}: { - emailProvider?: "plunk" | "resend"; -}) { +async function addFmaddonAuth() { await proofkitAuthInstaller(); mergeSettings({ auth: { type: "fmaddon" } }); } diff --git a/cli/src/installers/install-fm-addon.ts b/cli/src/installers/install-fm-addon.ts index 37849362..9345af7c 100644 --- a/cli/src/installers/install-fm-addon.ts +++ b/cli/src/installers/install-fm-addon.ts @@ -4,7 +4,6 @@ import chalk from "chalk"; import fs from "fs-extra"; import { PKG_ROOT } from "~/consts.js"; -import { getUserPkgManager } from "~/utils/getUserPkgManager.js"; import { logger } from "~/utils/logger.js"; export async function installFmAddon({ @@ -62,29 +61,22 @@ export async function installFmAddon({ if (addonName === "auth") { console.log( `${chalk.yellowBright( - "You must install the FM Add-on Auth addon in your FileMaker file." + "You must install the FM Add-on Auth addon in your FileMaker file to continue." )} ${chalk.dim("(Learn more: https://proofkit.dev/auth/fm-addon)")}` ); } else { console.log( `${chalk.yellowBright( - "You must install the ProofKit WebViewer addon in your FileMaker file." + "You must install the ProofKit WebViewer addon in your FileMaker file to continue." )} ${chalk.dim("(Learn more: https://proofkit.dev/webviewer)")}` ); } const steps = [ "Restart FileMaker Pro (if it's currently running)", `Open your FileMaker file, go to layout mode, and install the ${addonDisplayName} addon to the file`, - "Run the typegen command to add the types into your project:", + "Come back here to continue the installation", ]; steps.forEach((step, index) => { console.log(`${index + 1}. ${step}`); }); - - console.log(chalk.cyan(` ${getUserPkgManager()} typegen`)); - console.log(""); - - throw new Error( - "You must install the FM Add-on Auth addon in your FileMaker file." - ); } diff --git a/cli/src/installers/proofkit-auth.ts b/cli/src/installers/proofkit-auth.ts index 85ad45e9..3ab8d06a 100644 --- a/cli/src/installers/proofkit-auth.ts +++ b/cli/src/installers/proofkit-auth.ts @@ -1,4 +1,5 @@ import path from "path"; +import * as p from "@clack/prompts"; import { type OttoAPIKey } from "@proofgeist/fmdapi"; import chalk from "chalk"; import dotenv from "dotenv"; @@ -6,6 +7,7 @@ import fs from "fs-extra"; import { SyntaxKind, type SourceFile } from "ts-morph"; import { getLayouts } from "~/cli/fmdapi.js"; +import { abortIfCancel, UserAbortedError } from "~/cli/utils.js"; import { PKG_ROOT } from "~/consts.js"; import { addConfig, runCodegenCommand } from "~/generators/fmdapi.js"; import { injectTanstackQuery } from "~/generators/tanstack-query.js"; @@ -16,7 +18,7 @@ import { getSettings } from "~/utils/parseSettings.js"; import { formatAndSaveSourceFiles, getNewProject } from "~/utils/ts-morph.js"; import { addToHeaderSlot } from "./auth-shared.js"; import { installFmAddon } from "./install-fm-addon.js"; -import { installReactEmail } from "./react-email.js"; +import { installPlunk, installReactEmail } from "./react-email.js"; export const proofkitAuthInstaller = async () => { const projectDir = state.projectDir; @@ -105,7 +107,12 @@ export const proofkitAuthInstaller = async () => { projectDir, runCodegen: false, }); - await installReactEmail({ project }); + + if (state.emailProvider === "resend") { + await installReactEmail({ project }); + } else if (state.emailProvider === "plunk") { + await installPlunk({ project }); + } protectMainLayout( project.addSourceFileAtPath( @@ -115,12 +122,25 @@ export const proofkitAuthInstaller = async () => { await formatAndSaveSourceFiles(project); - const hasProofKitLayouts = await checkForProofKitLayouts(projectDir); - if (hasProofKitLayouts) { - await runCodegenCommand({ projectDir }); + let hasProofKitLayouts = false; + while (!hasProofKitLayouts) { + hasProofKitLayouts = await checkForProofKitLayouts(projectDir); + + if (!hasProofKitLayouts) { + const shouldContinue = abortIfCancel( + await p.confirm({ + message: + "I have followed the above instructions, continue installing", + initialValue: true, + active: "Continue", + inactive: "Abort", + }) + ); + + if (!shouldContinue) throw new UserAbortedError(); + } } - - await installDependencies({ projectDir }); + await runCodegenCommand({ projectDir }); }; function addToSafeActionClient(sourceFile?: SourceFile) { diff --git a/cli/template/extras/config/_eslint.js b/cli/template/extras/config/_eslint.js index 2303ad00..74dfa621 100644 --- a/cli/template/extras/config/_eslint.js +++ b/cli/template/extras/config/_eslint.js @@ -17,6 +17,7 @@ export const _initialConfig = { ], "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }], "@typescript-eslint/require-await": "off", + "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-misused-promises": [ "error", { checksVoidReturn: { attributes: false } }, diff --git a/cli/template/pages/nextjs/table-infinite-edit/actions.ts b/cli/template/pages/nextjs/table-infinite-edit/actions.ts new file mode 100644 index 00000000..ea11bcb4 --- /dev/null +++ b/cli/template/pages/nextjs/table-infinite-edit/actions.ts @@ -0,0 +1,84 @@ +"use server"; + +import { + __TYPE_NAME__, + __ZOD_TYPE_NAME__, +} from "@/config/schemas/__SOURCE_NAME__/__SCHEMA_NAME__"; +import { __CLIENT_NAME__ } from "@/config/schemas/__SOURCE_NAME__/client"; +import { __ACTION_CLIENT__ } from "@/server/safe-action"; +import { ListParams, Query } from "@proofgeist/fmdapi/dist/client-types.js"; +import dayjs from "dayjs"; +import { z } from "zod"; + +import { idFieldName } from "./schema"; + +const limit = 50; // raise or lower this number depending on how your layout performs +export const fetchData = __ACTION_CLIENT__ + .schema( + z.object({ + offset: z.number().catch(0), + sorting: z.array( + z.object({ id: z.string(), desc: z.boolean().default(false) }) + ), + columnFilters: z.array(z.object({ id: z.string(), value: z.unknown() })), + }) + ) + .action(async ({ parsedInput: { offset, sorting, columnFilters } }) => { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const getOptions: ListParams<__TYPE_NAME__, any> & { + query: Query<__TYPE_NAME__>[]; + } = { + limit, + offset, + query: [{ ["__FIRST_FIELD_NAME__"]: "*" }], + }; + + if (sorting.length > 0) { + getOptions.sort = sorting.map(({ id, desc }) => ({ + fieldName: id as keyof __TYPE_NAME__, + sortOrder: desc ? "descend" : "ascend", + })); + } + + if (columnFilters.length > 0) { + getOptions.query = columnFilters + .map(({ id, value }) => { + if (typeof value === "string") { + return { + [id]: value, + }; + } else if (typeof value === "object" && value instanceof Date) { + return { + [id]: dayjs(value).format("YYYY+MM+DD"), + }; + } + return null; + }) + .filter(Boolean) as Query[]; + } + + const data = await __CLIENT_NAME__.find(getOptions); + + return { + data: data.data, + hasNextPage: data.dataInfo.foundCount > limit + offset, + totalCount: data.dataInfo.foundCount, + }; + }); + +export const updateRecord = __ACTION_CLIENT__ + .schema(__ZOD_TYPE_NAME__.partial()) + .action(async ({ parsedInput }) => { + const id = parsedInput[idFieldName]; + delete parsedInput[idFieldName]; // this ensures the id field value is not included in the updated fieldData + const data = parsedInput; + + const { + data: { recordId }, + } = await __CLIENT_NAME__.findOne({ query: { [idFieldName]: `==${id}` } }); + + return await __CLIENT_NAME__.update({ + recordId, + fieldData: data, + }); + }); diff --git a/cli/template/pages/nextjs/table-infinite-edit/page.tsx b/cli/template/pages/nextjs/table-infinite-edit/page.tsx new file mode 100644 index 00000000..cec39a4d --- /dev/null +++ b/cli/template/pages/nextjs/table-infinite-edit/page.tsx @@ -0,0 +1,23 @@ +import { Stack, Text, Code } from "@mantine/core"; +import React from "react"; + +import TableContent from "./table"; +import { idFieldName } from "./schema"; + +export default async function TablePage() { + return ( + +
+ + This table allows editing. Double-click on a cell to edit the value. + + + NOTE: This feature requires a primary key field on your API layout. If your + primary key field is not {idFieldName}, update the + idFieldName variable in the schema.ts file. + +
+ +
+ ); +} \ No newline at end of file diff --git a/cli/template/pages/nextjs/table-infinite-edit/query.ts b/cli/template/pages/nextjs/table-infinite-edit/query.ts new file mode 100644 index 00000000..53d86d1c --- /dev/null +++ b/cli/template/pages/nextjs/table-infinite-edit/query.ts @@ -0,0 +1,81 @@ +"use client"; + +import { showErrorNotification } from "@/utils/notification-helpers"; +import { + useInfiniteQuery, + useMutation, + useQueryClient, +} from "@tanstack/react-query"; +import type { + MRT_ColumnFiltersState, + MRT_SortingState, +} from "mantine-react-table"; +import { useMemo } from "react"; + +import { fetchData, updateRecord } from "./actions"; +import { idFieldName } from "./schema"; + +export function useAllData({ + sorting, + columnFilters, +}: { + sorting: MRT_SortingState; + columnFilters: MRT_ColumnFiltersState; +}) { + const queryKey = ["all-__SCHEMA_NAME__", sorting, columnFilters]; + // useInfiniteQuery is used to help with automatic pagination + const qr = useInfiniteQuery({ + queryKey, + queryFn: async ({ pageParam: offset }) => { + const result = await fetchData({ offset, sorting, columnFilters }); + if (!result) throw new Error(`Failed to fetch __SCHEMA_NAME__`); + if (!result.data) throw new Error(`No data found for __SCHEMA_NAME__`); + return result?.data; + }, + retry: false, + initialPageParam: 0, + getNextPageParam: (lastPage, pages) => + lastPage.hasNextPage + ? pages.flatMap((page) => page.data).length + : undefined, + }); + + const flatData = useMemo( + () => + qr.data?.pages.flatMap((page) => page.data).map((o) => o.fieldData) ?? [], + [qr.data] + ); + const totalFetched = flatData.length; + const totalDBRowCount = qr.data?.pages?.[0]?.totalCount ?? 0; + + const queryClient = useQueryClient(); + + const updateRecordMutation = useMutation({ + mutationFn: updateRecord, + onMutate: async (newRecord) => { + // Cancel any outgoing refetches + await queryClient.cancelQueries({ queryKey }); + + // Optimistically update to the new value + queryClient.setQueryData(queryKey, (old) => { + if (!old) return old; + return { + ...old, + pages: old.pages.map((page) => ({ + ...page, + data: page.data.map((row) => + row.fieldData[idFieldName] === newRecord[idFieldName] + ? { ...row, fieldData: { ...row.fieldData, ...newRecord } } + : row, + ), + })), + }; + }); + }, + onError: () => { + showErrorNotification("Failed to update record"); + }, + }); + + return { ...qr, data: flatData, totalDBRowCount, totalFetched, updateRecord: updateRecordMutation.mutate }; +} diff --git a/cli/template/pages/nextjs/table-infinite-edit/schema.ts b/cli/template/pages/nextjs/table-infinite-edit/schema.ts new file mode 100644 index 00000000..28c55f5c --- /dev/null +++ b/cli/template/pages/nextjs/table-infinite-edit/schema.ts @@ -0,0 +1,4 @@ +import { type __TYPE_NAME__ } from "@/config/schemas/__SOURCE_NAME__/__SCHEMA_NAME__"; + +// TODO: Make sure this variable is properly set to your primary key field +export const idFieldName: keyof __TYPE_NAME__ = "__FIRST_FIELD_NAME__"; diff --git a/cli/template/pages/nextjs/table-infinite-edit/table.tsx b/cli/template/pages/nextjs/table-infinite-edit/table.tsx new file mode 100644 index 00000000..7cfea318 --- /dev/null +++ b/cli/template/pages/nextjs/table-infinite-edit/table.tsx @@ -0,0 +1,130 @@ +"use client"; + +import { __TYPE_NAME__ } from "@/config/schemas/__SOURCE_NAME__/__SCHEMA_NAME__"; +import { Text } from "@mantine/core"; +import { + createMRTColumnHelper, + MantineReactTable, + useMantineReactTable, + type MRT_Cell, + type MRT_ColumnDef, + type MRT_ColumnFiltersState, + type MRT_RowVirtualizer, + type MRT_SortingState, +} from "mantine-react-table"; +import React, { + useCallback, + useEffect, + useRef, + useState, + type UIEvent, +} from "react"; + +import { useAllData } from "./query"; +import { idFieldName } from "./schema"; + +type TData = __TYPE_NAME__; + +const columns: MRT_ColumnDef[] = []; + +export default function MyTable() { + const tableContainerRef = useRef(null); + const rowVirtualizerInstanceRef = + useRef>(null); + + const [sorting, setSorting] = useState([]); + const [columnFilters, setColumnFilters] = useState( + [] + ); + + const { + data, + totalDBRowCount, + totalFetched, + isLoading, + isFetching, + fetchNextPage, + updateRecord, + } = useAllData({ sorting, columnFilters }); + + async function handleSaveCell(cell: MRT_Cell, value: unknown) { + updateRecord({ + [idFieldName]: cell.row.id, + [cell.column.id]: value, + }); + } + + const table = useMantineReactTable({ + data, + columns, + rowCount: totalDBRowCount, + enableRowVirtualization: true, // only render the rows that are visible on screen to improve performance + state: { isLoading, sorting, showProgressBars: isFetching, columnFilters }, + enableGlobalFilter: false, // doesn't work as easily with server-side filters, it's better to filter the specific columns + onSortingChange: setSorting, + onColumnFiltersChange: setColumnFilters, + enablePagination: false, // hide pagination buttons + enableStickyHeader: true, + mantineBottomToolbarProps: { style: { alignItems: "center" } }, + renderBottomToolbarCustomActions: () => + !isLoading ? ( + + Fetched {totalFetched} of {totalDBRowCount} + + ) : null, + mantineTableContainerProps: ({ table }) => { + return { + h: table.getState().isFullScreen + ? "100%" + : `calc(100vh - var(--app-shell-header-height) - 10rem)`, // may need to adjust this height if you have more elements on your page + ref: tableContainerRef, + onScroll: ( + event: UIEvent //add an event listener to the table container element + ) => fetchMoreOnBottomReached(event.target as HTMLDivElement), + }; + }, + + /** Inline editing functionality */ + enableEditing: true, + editDisplayMode: "cell", + getRowId: (row) => row[idFieldName], + mantineEditTextInputProps: ({ cell }) => ({ + // onBlur is more efficient (only called when you leave the field) + // onChange event could be used for other types of edits, like dropdowns + onBlur: (event) => { + handleSaveCell(cell, event.target.value); + }, + }), + }); + + // called on scroll and possibly on mount to fetch more data as the user scrolls and reaches bottom of table + const fetchMoreOnBottomReached = useCallback( + (containerRefElement?: HTMLDivElement | null) => { + if (containerRefElement) { + const { scrollHeight, scrollTop, clientHeight } = containerRefElement; + // once the user has scrolled within 400px of the bottom of the table, fetch more data + if ( + scrollHeight - scrollTop - clientHeight < 400 && + !isFetching && + totalFetched < totalDBRowCount + ) { + void fetchNextPage(); + } + } + }, + [fetchNextPage, isFetching, totalFetched, totalDBRowCount] + ); + + // scroll to top of table when sorting or filters change + useEffect(() => { + if (rowVirtualizerInstanceRef.current) { + try { + rowVirtualizerInstanceRef.current.scrollToIndex(0); + } catch (e) { + console.error(e); + } + } + }, [sorting, columnFilters]); + + return ; +} diff --git a/cli/template/pages/nextjs/table-infinite/actions.ts b/cli/template/pages/nextjs/table-infinite/actions.ts index 1a9dadca..843f570d 100644 --- a/cli/template/pages/nextjs/table-infinite/actions.ts +++ b/cli/template/pages/nextjs/table-infinite/actions.ts @@ -1,11 +1,11 @@ "use server"; -import { __CLIENT_NAME__ } from "@/config/schemas/__SOURCE_NAME__/client"; import { __TYPE_NAME__ } from "@/config/schemas/__SOURCE_NAME__/__SCHEMA_NAME__"; +import { __CLIENT_NAME__ } from "@/config/schemas/__SOURCE_NAME__/client"; import { __ACTION_CLIENT__ } from "@/server/safe-action"; import { ListParams, Query } from "@proofgeist/fmdapi/dist/client-types.js"; -import { z } from "zod"; import dayjs from "dayjs"; +import { z } from "zod"; const limit = 50; // raise or lower this number depending on how your layout performs export const fetchData = __ACTION_CLIENT__ @@ -19,15 +19,18 @@ export const fetchData = __ACTION_CLIENT__ }) ) .action(async ({ parsedInput: { offset, sorting, columnFilters } }) => { - const getOptions: ListParams<__TYPE_NAME__, any> & { query: Query[] } = { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const getOptions: ListParams<__TYPE_NAME__, any> & { + query: Query<__TYPE_NAME__>[]; + } = { limit, offset, - query: [{ id: "*" }], + query: [{ ["__FIRST_FIELD_NAME__"]: "*" }], }; if (sorting.length > 0) { getOptions.sort = sorting.map(({ id, desc }) => ({ - fieldName: "__FIRST_FIELD_NAME__" as keyof __TYPE_NAME__, + fieldName: id as keyof __TYPE_NAME__, sortOrder: desc ? "descend" : "ascend", })); } diff --git a/config/templates.ts b/config/templates.ts index e11f36f6..09c274e1 100644 --- a/config/templates.ts +++ b/config/templates.ts @@ -39,6 +39,13 @@ export const nextjsTemplates: Record = { templatePath: "nextjs/table-infinite", postIntallFn: postInstallTableInfinite, }, + tableInfiniteEdit: { + requireData: true, + label: "Infinite Table (editable)", + hint: "Automatically load more records when the user scrolls to the bottom with inline edit functionality", + templatePath: "nextjs/table-infinite-edit", + postIntallFn: postInstallTableInfinite, + }, }; export const wvTemplates: Record = { From 95a6442b2072d04d209b76d0dc454fa3659ec731 Mon Sep 17 00:00:00 2001 From: Eric Luce <37158449+eluce2@users.noreply.github.com> Date: Tue, 11 Mar 2025 11:51:20 -0500 Subject: [PATCH 2/2] fix react email installer --- cli/src/installers/proofkit-auth.ts | 9 +++------ cli/src/installers/react-email.ts | 2 +- cli/template/pages/nextjs/table-infinite-edit/table.tsx | 2 +- 3 files changed, 5 insertions(+), 8 deletions(-) diff --git a/cli/src/installers/proofkit-auth.ts b/cli/src/installers/proofkit-auth.ts index 3ab8d06a..e571a020 100644 --- a/cli/src/installers/proofkit-auth.ts +++ b/cli/src/installers/proofkit-auth.ts @@ -18,7 +18,7 @@ import { getSettings } from "~/utils/parseSettings.js"; import { formatAndSaveSourceFiles, getNewProject } from "~/utils/ts-morph.js"; import { addToHeaderSlot } from "./auth-shared.js"; import { installFmAddon } from "./install-fm-addon.js"; -import { installPlunk, installReactEmail } from "./react-email.js"; +import { installEmailProvider, installPlunk } from "./react-email.js"; export const proofkitAuthInstaller = async () => { const projectDir = state.projectDir; @@ -108,11 +108,8 @@ export const proofkitAuthInstaller = async () => { runCodegen: false, }); - if (state.emailProvider === "resend") { - await installReactEmail({ project }); - } else if (state.emailProvider === "plunk") { - await installPlunk({ project }); - } + // install email files based on the email provider in state + await installEmailProvider({ project }); protectMainLayout( project.addSourceFileAtPath( diff --git a/cli/src/installers/react-email.ts b/cli/src/installers/react-email.ts index 3e93bb2a..37f00c30 100644 --- a/cli/src/installers/react-email.ts +++ b/cli/src/installers/react-email.ts @@ -13,7 +13,7 @@ import { addToEnv } from "~/utils/addToEnvs.js"; import { logger } from "~/utils/logger.js"; import { formatAndSaveSourceFiles, getNewProject } from "~/utils/ts-morph.js"; -export async function installReactEmail({ ...args }: { project?: Project }) { +export async function installEmailProvider({ ...args }: { project?: Project }) { const projectDir = state.projectDir; addPackageDependency({ dependencies: ["@react-email/components", "@react-email/render"], diff --git a/cli/template/pages/nextjs/table-infinite-edit/table.tsx b/cli/template/pages/nextjs/table-infinite-edit/table.tsx index 7cfea318..aeb29899 100644 --- a/cli/template/pages/nextjs/table-infinite-edit/table.tsx +++ b/cli/template/pages/nextjs/table-infinite-edit/table.tsx @@ -76,7 +76,7 @@ export default function MyTable() { return { h: table.getState().isFullScreen ? "100%" - : `calc(100vh - var(--app-shell-header-height) - 10rem)`, // may need to adjust this height if you have more elements on your page + : `calc(100vh - var(--app-shell-header-height) - 13rem)`, // may need to adjust this height if you have more elements on your page ref: tableContainerRef, onScroll: ( event: UIEvent //add an event listener to the table container element