From d315ad94c0baf04990d3ab3972d77f63d7a869d7 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Fri, 8 Aug 2025 20:28:25 +0000 Subject: [PATCH] Checkpoint before follow-up message Co-authored-by: eric.luce --- .changeset/ui-default-shadcn.md | 5 + packages/cli/src/cli/add/auth.ts | 5 + packages/cli/src/cli/add/index.ts | 9 ++ packages/cli/src/cli/add/page/index.ts | 5 + packages/cli/src/cli/init.ts | 45 +++++-- packages/cli/src/consts.ts | 5 + packages/cli/src/helpers/createProject.ts | 58 ++++++-- .../cli/src/helpers/installDependencies.ts | 26 +++- packages/cli/src/helpers/scaffoldProject.ts | 4 +- packages/cli/src/helpers/shadcn-cli.ts | 18 +++ .../src/installers/dependencyVersionMap.ts | 11 ++ packages/cli/src/state.ts | 1 + .../cli/src/utils/addPackageDependency.ts | 5 +- packages/cli/src/utils/parseSettings.ts | 15 ++- .../{nextjs => nextjs-mantine}/.prettierrc | 0 .../{nextjs => nextjs-mantine}/README.md | 0 .../{nextjs => nextjs-mantine}/_gitignore | 0 .../components.json | 0 .../{nextjs => nextjs-mantine}/next.config.ts | 0 .../{nextjs => nextjs-mantine}/package.json | 0 .../postcss.config.cjs | 0 .../cli/template/nextjs-mantine/proofkit.json | 7 + .../public/favicon.ico | Bin .../public/proofkit.png | Bin .../src/app/(main)/layout.tsx | 0 .../src/app/(main)/page.tsx | 0 .../src/app/layout.tsx | 0 .../src/app/navigation.tsx | 0 .../src/components/AppLogo.tsx | 0 .../components/AppShell/internal/AppShell.tsx | 0 .../AppShell/internal/Header.module.css | 0 .../components/AppShell/internal/Header.tsx | 0 .../AppShell/internal/HeaderMobileMenu.tsx | 0 .../AppShell/internal/HeaderNavLink.tsx | 0 .../components/AppShell/internal/config.ts | 0 .../AppShell/slot-header-center.tsx | 0 .../components/AppShell/slot-header-left.tsx | 0 .../AppShell/slot-header-mobile-content.tsx | 0 .../components/AppShell/slot-header-right.tsx | 0 .../src/config/env.ts | 0 .../src/config/theme/globals.css | 0 .../src/config/theme/mantine-theme.ts | 0 .../src/server/safe-action.ts | 0 .../src/utils/notification-helpers.ts | 0 .../src/utils/styles.ts | 0 .../{nextjs => nextjs-mantine}/tsconfig.json | 0 .../cli/template/nextjs-shadcn/.prettierrc | 3 + packages/cli/template/nextjs-shadcn/README.md | 27 ++++ .../cli/template/nextjs-shadcn/_gitignore | 37 +++++ .../template/nextjs-shadcn/components.json | 21 +++ .../cli/template/nextjs-shadcn/next.config.ts | 8 ++ .../cli/template/nextjs-shadcn/package.json | 42 ++++++ .../template/nextjs-shadcn/postcss.config.cjs | 5 + .../{nextjs => nextjs-shadcn}/proofkit.json | 1 + .../template/nextjs-shadcn/public/favicon.ico | Bin 0 -> 15086 bytes .../nextjs-shadcn/public/proofkit.png | Bin 0 -> 52140 bytes .../nextjs-shadcn/src/app/(main)/layout.tsx | 6 + .../nextjs-shadcn/src/app/(main)/page.tsx | 116 ++++++++++++++++ .../template/nextjs-shadcn/src/app/layout.tsx | 38 ++++++ .../nextjs-shadcn/src/app/navigation.tsx | 12 ++ .../nextjs-shadcn/src/components/AppLogo.tsx | 6 + .../components/AppShell/internal/AppShell.tsx | 23 ++++ .../AppShell/internal/Header.module.css | 33 +++++ .../components/AppShell/internal/Header.tsx | 30 +++++ .../AppShell/internal/HeaderMobileMenu.tsx | 25 ++++ .../AppShell/internal/HeaderNavLink.tsx | 31 +++++ .../components/AppShell/internal/config.ts | 1 + .../AppShell/slot-header-center.tsx | 13 ++ .../components/AppShell/slot-header-left.tsx | 23 ++++ .../AppShell/slot-header-mobile-content.tsx | 43 ++++++ .../components/AppShell/slot-header-right.tsx | 25 ++++ .../src/components/providers.tsx | 13 ++ .../src/components/theme-provider.tsx | 11 ++ .../template/nextjs-shadcn/src/config/env.ts | 13 ++ .../src/config/theme/globals.css | 126 ++++++++++++++++++ .../nextjs-shadcn/src/server/safe-action.ts | 3 + .../src/utils/notification-helpers.ts | 15 +++ .../nextjs-shadcn/src/utils/styles.ts | 6 + .../cli/template/nextjs-shadcn/tsconfig.json | 40 ++++++ packages/cli/tsup.config.ts | 3 +- 80 files changed, 981 insertions(+), 37 deletions(-) create mode 100644 .changeset/ui-default-shadcn.md create mode 100644 packages/cli/src/helpers/shadcn-cli.ts rename packages/cli/template/{nextjs => nextjs-mantine}/.prettierrc (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/README.md (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/_gitignore (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/components.json (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/next.config.ts (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/package.json (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/postcss.config.cjs (100%) create mode 100644 packages/cli/template/nextjs-mantine/proofkit.json rename packages/cli/template/{nextjs => nextjs-mantine}/public/favicon.ico (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/public/proofkit.png (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/app/(main)/layout.tsx (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/app/(main)/page.tsx (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/app/layout.tsx (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/app/navigation.tsx (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/components/AppLogo.tsx (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/components/AppShell/internal/AppShell.tsx (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/components/AppShell/internal/Header.module.css (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/components/AppShell/internal/Header.tsx (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/components/AppShell/internal/HeaderMobileMenu.tsx (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/components/AppShell/internal/HeaderNavLink.tsx (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/components/AppShell/internal/config.ts (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/components/AppShell/slot-header-center.tsx (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/components/AppShell/slot-header-left.tsx (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/components/AppShell/slot-header-mobile-content.tsx (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/components/AppShell/slot-header-right.tsx (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/config/env.ts (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/config/theme/globals.css (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/config/theme/mantine-theme.ts (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/server/safe-action.ts (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/utils/notification-helpers.ts (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/src/utils/styles.ts (100%) rename packages/cli/template/{nextjs => nextjs-mantine}/tsconfig.json (100%) create mode 100644 packages/cli/template/nextjs-shadcn/.prettierrc create mode 100644 packages/cli/template/nextjs-shadcn/README.md create mode 100644 packages/cli/template/nextjs-shadcn/_gitignore create mode 100644 packages/cli/template/nextjs-shadcn/components.json create mode 100644 packages/cli/template/nextjs-shadcn/next.config.ts create mode 100644 packages/cli/template/nextjs-shadcn/package.json create mode 100644 packages/cli/template/nextjs-shadcn/postcss.config.cjs rename packages/cli/template/{nextjs => nextjs-shadcn}/proofkit.json (87%) create mode 100644 packages/cli/template/nextjs-shadcn/public/favicon.ico create mode 100644 packages/cli/template/nextjs-shadcn/public/proofkit.png create mode 100644 packages/cli/template/nextjs-shadcn/src/app/(main)/layout.tsx create mode 100644 packages/cli/template/nextjs-shadcn/src/app/(main)/page.tsx create mode 100644 packages/cli/template/nextjs-shadcn/src/app/layout.tsx create mode 100644 packages/cli/template/nextjs-shadcn/src/app/navigation.tsx create mode 100644 packages/cli/template/nextjs-shadcn/src/components/AppLogo.tsx create mode 100644 packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/AppShell.tsx create mode 100644 packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/Header.module.css create mode 100644 packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/Header.tsx create mode 100644 packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/HeaderMobileMenu.tsx create mode 100644 packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/HeaderNavLink.tsx create mode 100644 packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/config.ts create mode 100644 packages/cli/template/nextjs-shadcn/src/components/AppShell/slot-header-center.tsx create mode 100644 packages/cli/template/nextjs-shadcn/src/components/AppShell/slot-header-left.tsx create mode 100644 packages/cli/template/nextjs-shadcn/src/components/AppShell/slot-header-mobile-content.tsx create mode 100644 packages/cli/template/nextjs-shadcn/src/components/AppShell/slot-header-right.tsx create mode 100644 packages/cli/template/nextjs-shadcn/src/components/providers.tsx create mode 100644 packages/cli/template/nextjs-shadcn/src/components/theme-provider.tsx create mode 100644 packages/cli/template/nextjs-shadcn/src/config/env.ts create mode 100644 packages/cli/template/nextjs-shadcn/src/config/theme/globals.css create mode 100644 packages/cli/template/nextjs-shadcn/src/server/safe-action.ts create mode 100644 packages/cli/template/nextjs-shadcn/src/utils/notification-helpers.ts create mode 100644 packages/cli/template/nextjs-shadcn/src/utils/styles.ts create mode 100644 packages/cli/template/nextjs-shadcn/tsconfig.json diff --git a/.changeset/ui-default-shadcn.md b/.changeset/ui-default-shadcn.md new file mode 100644 index 00000000..6a54d283 --- /dev/null +++ b/.changeset/ui-default-shadcn.md @@ -0,0 +1,5 @@ +--- +"@proofkit/cli": minor +--- + +CLI defaults to shadcn/ui for new projects. Legacy Mantine templates are still available via a hidden `--ui mantine` flag during `init`. The selected UI is persisted in `proofkit.json` as `ui`. Existing projects using Mantine are auto-detected and remain fully supported. For shadcn-based projects, adding new pages or auth via `proofkit add` is temporarily disabled while we work on a new component system. \ No newline at end of file diff --git a/packages/cli/src/cli/add/auth.ts b/packages/cli/src/cli/add/auth.ts index 8b389ba0..9e5208dd 100644 --- a/packages/cli/src/cli/add/auth.ts +++ b/packages/cli/src/cli/add/auth.ts @@ -14,6 +14,11 @@ export async function runAddAuthAction() { if (settings.appType !== "browser") { return p.cancel(`Auth is not supported for your app type.`); } + if (settings.ui === "shadcn") { + return p.cancel( + "Adding auth is not yet supported for shadcn-based projects." + ); + } const authType = state.authType ?? diff --git a/packages/cli/src/cli/add/index.ts b/packages/cli/src/cli/add/index.ts index 5989d7b5..2d1301de 100644 --- a/packages/cli/src/cli/add/index.ts +++ b/packages/cli/src/cli/add/index.ts @@ -47,6 +47,15 @@ export const runAdd = async (name: string | undefined) => { }) ); + // For shadcn projects, block adding new pages or auth for now + if (settings.ui === "shadcn") { + if (addType === "page" || addType === "auth") { + return p.cancel( + "Adding new pages or auth is not yet supported for shadcn-based projects." + ); + } + } + if (addType === "auth") { await runAddAuthAction(); } else if (addType === "data") { diff --git a/packages/cli/src/cli/add/page/index.ts b/packages/cli/src/cli/add/page/index.ts index a4eec2dd..79c9ca6f 100644 --- a/packages/cli/src/cli/add/page/index.ts +++ b/packages/cli/src/cli/add/page/index.ts @@ -29,6 +29,11 @@ export const runAddPageAction = async (opts?: { const projectDir = state.projectDir; const settings = getSettings(); + if (settings.ui === "shadcn") { + return p.cancel( + "Adding pages is not yet supported for shadcn-based projects." + ); + } const templates = state.appType === "browser" diff --git a/packages/cli/src/cli/init.ts b/packages/cli/src/cli/init.ts index e8e05c09..073e28e4 100644 --- a/packages/cli/src/cli/init.ts +++ b/packages/cli/src/cli/init.ts @@ -14,6 +14,7 @@ import { initializeGit } from "~/helpers/git.js"; import { installDependencies } from "~/helpers/installDependencies.js"; import { logNextSteps } from "~/helpers/logNextSteps.js"; import { setImportAlias } from "~/helpers/setImportAlias.js"; +import { getRegistryUrl, shadcnInstall } from "~/helpers/shadcn-cli.js"; import { buildPkgInstallerMap } from "~/installers/index.js"; import { ensureWebViewerAddonInstalled } from "~/installers/proofkit-webviewer.js"; import { initProgramState, state } from "~/state.js"; @@ -21,6 +22,11 @@ import { addPackageDependency } from "~/utils/addPackageDependency.js"; import { getVersion } from "~/utils/getProofKitVersion.js"; import { getUserPkgManager } from "~/utils/getUserPkgManager.js"; import { parseNameAndPath } from "~/utils/parseNameAndPath.js"; +import { + getSettings, + setSettings, + type Settings, +} from "~/utils/parseSettings.js"; import { validateAppName } from "~/utils/validateAppName.js"; import { promptForFileMakerDataSource } from "./add/data-source/filemaker.js"; import { abortIfCancel } from "./utils.js"; @@ -39,6 +45,8 @@ interface CliFlags { fmServerURL: string; auth: "none" | "next-auth" | "clerk"; dataSource?: "filemaker" | "none" | "supabase"; + /** @internal UI library selection; hidden flag */ + ui?: "shadcn" | "mantine"; /** @internal Used in CI. */ CI: boolean; /** @internal Used in CI. */ @@ -72,6 +80,7 @@ const defaultOptions: CliFlags = { dataApiKey: "", fmServerURL: "", dataSource: undefined, + ui: "shadcn", }; export const makeInitCommand = () => { @@ -82,6 +91,8 @@ export const makeInitCommand = () => { "The name of the application, as well as the name of the directory to create" ) .option("--appType [type]", "The type of app to create", undefined) + // hidden UI selector; default is shadcn; pass --ui mantine to opt-in legacy Mantine templates + .option("--ui [ui]", undefined, undefined) .option("--server [url]", "The URL of your FileMaker Server", undefined) .option( "--adminApiKey [key]", @@ -166,6 +177,8 @@ type ProofKitPackageJSON = PackageJson & { export const runInit = async (name?: string, opts?: CliFlags) => { const pkgManager = getUserPkgManager(); const cliOptions = opts ?? defaultOptions; + // capture ui choice early into state + state.ui = (cliOptions.ui ?? "shadcn") as "shadcn" | "mantine"; const projectName = name || @@ -211,13 +224,7 @@ export const runInit = async (name?: string, opts?: CliFlags) => { noInstall: cliOptions.noInstall, appRouter: cliOptions.appRouter, }); - state.projectDir = projectDir; setImportAlias(projectDir, "@/"); - addPackageDependency({ - dependencies: ["@proofkit/cli", "@types/node"], - devMode: true, - projectDir, - }); // Write name to package.json const pkgJson = fs.readJSONSync( @@ -238,6 +245,19 @@ export const runInit = async (name?: string, opts?: CliFlags) => { spaces: 2, }); + // Ensure proofkit.json exists with initial settings including ui + const initialSettings: Settings = { + appType: state.appType ?? "browser", + ui: (state.ui as "shadcn" | "mantine") ?? "shadcn", + auth: { type: "none" }, + envFile: ".env", + dataSources: [], + tanstackQuery: false, + replacedMainPage: false, + appliedUpgrades: ["cursorRules"], + }; + const { registryUrl } = setSettings(initialSettings); + // for webviewer apps FM is required, so don't ask let dataSource = state.appType === "webviewer" ? "filemaker" : cliOptions.dataSource; @@ -285,11 +305,18 @@ export const runInit = async (name?: string, opts?: CliFlags) => { await askForAuth({ projectDir }); - if (!cliOptions.noInstall) { - await installDependencies({ projectDir }); - await runCodegenCommand(); + await installDependencies({ projectDir }); + + if (state.ui === "shadcn") { + await shadcnInstall([ + `${getRegistryUrl()}/r/mode-toggle`, + "sonner", + "button", + ]); } + await runCodegenCommand(); + if (!cliOptions.noGit) { await initializeGit(projectDir); } diff --git a/packages/cli/src/consts.ts b/packages/cli/src/consts.ts index 66334b7c..dfacca41 100644 --- a/packages/cli/src/consts.ts +++ b/packages/cli/src/consts.ts @@ -27,3 +27,8 @@ ${" ".repeat(61 - versionCharLength)}v${version} `; export const DEFAULT_APP_NAME = "my-proofkit-app"; export const CREATE_FM_APP = cliName; + +export const DEFAULT_REGISTRY_URL = + process.env.NODE_ENV === "development" + ? "http://localhost:3005" + : "https://proofkit.dev"; diff --git a/packages/cli/src/helpers/createProject.ts b/packages/cli/src/helpers/createProject.ts index ea397701..7f57dae1 100644 --- a/packages/cli/src/helpers/createProject.ts +++ b/packages/cli/src/helpers/createProject.ts @@ -8,6 +8,7 @@ import { state } from "~/state.js"; import { addPackageDependency } from "~/utils/addPackageDependency.js"; import { getUserPkgManager } from "~/utils/getUserPkgManager.js"; import { replaceTextInFiles } from "./replaceText.js"; +import { shadcnInstall } from "./shadcn-cli.js"; interface CreateProjectOptions { projectName: string; @@ -34,9 +35,14 @@ export const createBareProject = async ({ noInstall, }); - // Add new base dependencies for Tailwind v4 and shadcn/ui + addPackageDependency({ + dependencies: ["@proofkit/cli", "@types/node"], + devMode: true, + }); + + // Add new base dependencies for Tailwind v4 and shadcn/ui or legacy Mantine // These should match the plan and dependencyVersionMap - const BASE_DEPS = [ + const SHADCN_BASE_DEPS = [ "@radix-ui/react-slot", "@tailwindcss/postcss", "class-variance-authority", @@ -45,22 +51,48 @@ export const createBareProject = async ({ "tailwind-merge", "tailwindcss", "tw-animate-css", + "next-themes", ] as AvailableDependencies[]; - const BASE_DEV_DEPS = [ + const SHADCN_BASE_DEV_DEPS = [ "prettier", "prettier-plugin-tailwindcss", ] as AvailableDependencies[]; - addPackageDependency({ - dependencies: BASE_DEPS, - devMode: false, - projectDir: state.projectDir, - }); - addPackageDependency({ - dependencies: BASE_DEV_DEPS, - devMode: true, - projectDir: state.projectDir, - }); + const MANTINE_DEPS = [ + "@mantine/core", + "@mantine/dates", + "@mantine/hooks", + "@mantine/modals", + "@mantine/notifications", + "mantine-react-table", + ] as AvailableDependencies[]; + const MANTINE_DEV_DEPS = [ + "postcss", + "postcss-preset-mantine", + "postcss-simple-vars", + ] as AvailableDependencies[]; + + if (state.ui === "mantine") { + addPackageDependency({ + dependencies: MANTINE_DEPS, + devMode: false, + }); + addPackageDependency({ + dependencies: MANTINE_DEV_DEPS, + devMode: true, + }); + } else if (state.ui === "shadcn") { + addPackageDependency({ + dependencies: SHADCN_BASE_DEPS, + devMode: false, + }); + addPackageDependency({ + dependencies: SHADCN_BASE_DEV_DEPS, + devMode: true, + }); + } else { + throw new Error(`Unsupported UI library: ${state.ui}`); + } // Install the selected packages installPackages({ diff --git a/packages/cli/src/helpers/installDependencies.ts b/packages/cli/src/helpers/installDependencies.ts index 83a98605..f3bb1ac9 100644 --- a/packages/cli/src/helpers/installDependencies.ts +++ b/packages/cli/src/helpers/installDependencies.ts @@ -16,6 +16,7 @@ const execWithSpinner = async ( args?: string[]; stdout?: StdoutStderrOption; onDataHandle?: (spinner: Ora) => (data: Buffer) => void; + loadingMessage?: string; } ) => { const { onDataHandle, args = ["install"], stdout = "pipe" } = options; @@ -24,7 +25,9 @@ const execWithSpinner = async ( args.push("--prefer-offline"); } - const spinner = ora(`Running ${pkgManager} ${args.join(" ")} ...`).start(); + const spinner = ora( + options.loadingMessage ?? `Running ${pkgManager} ${args.join(" ")} ...` + ).start(); const subprocess = execa(pkgManager, args, { cwd: projectDir, stdout }); await new Promise((res, rej) => { @@ -93,14 +96,20 @@ export const installDependencies = async (args?: { projectDir?: string }) => { export const runExecCommand = async ({ command, - projectDir, + projectDir = state.projectDir, successMessage, + loadingMessage, }: { command: string[]; - projectDir: string; + projectDir?: string; successMessage?: string; + loadingMessage?: string; }) => { - const spinner = await _runExecCommand({ projectDir, command }); + const spinner = await _runExecCommand({ + projectDir, + command, + loadingMessage, + }); // If the spinner was used to show the progress, use succeed method on it // If not, use the succeed on a new spinner @@ -116,10 +125,12 @@ export const runExecCommand = async ({ export const _runExecCommand = async ({ projectDir, command, + loadingMessage, }: { projectDir: string; exec?: boolean; command: string[]; + loadingMessage?: string; }): Promise => { const pkgManager = getUserPkgManager(); switch (pkgManager) { @@ -133,8 +144,9 @@ export const _runExecCommand = async ({ return null; // When using yarn or pnpm, use the stdout stream and ora spinner to show the progress case "pnpm": - return execWithSpinner(projectDir, "pnpx", { - args: [...command], + return execWithSpinner(projectDir, "pnpm", { + args: ["dlx", ...command], + loadingMessage, onDataHandle: (spinner) => (data) => { const text = data.toString(); @@ -148,6 +160,7 @@ export const _runExecCommand = async ({ case "yarn": return execWithSpinner(projectDir, pkgManager, { args: [...command], + loadingMessage, onDataHandle: (spinner) => (data) => { spinner.text = data.toString(); }, @@ -157,6 +170,7 @@ export const _runExecCommand = async ({ return execWithSpinner(projectDir, "bunx", { stdout: "ignore", args: [...command], + loadingMessage, }); } }; diff --git a/packages/cli/src/helpers/scaffoldProject.ts b/packages/cli/src/helpers/scaffoldProject.ts index 8ea7ed4c..c185197e 100644 --- a/packages/cli/src/helpers/scaffoldProject.ts +++ b/packages/cli/src/helpers/scaffoldProject.ts @@ -20,7 +20,9 @@ export const scaffoldProject = async ({ const srcDir = path.join( PKG_ROOT, - state.appType === "browser" ? "template/nextjs" : "template/vite-wv" + state.appType === "browser" + ? `template/${state.ui === "mantine" ? "nextjs-mantine" : "nextjs-shadcn"}` + : "template/vite-wv" ); if (!noInstall) { diff --git a/packages/cli/src/helpers/shadcn-cli.ts b/packages/cli/src/helpers/shadcn-cli.ts new file mode 100644 index 00000000..27661862 --- /dev/null +++ b/packages/cli/src/helpers/shadcn-cli.ts @@ -0,0 +1,18 @@ +import { DEFAULT_REGISTRY_URL } from "~/consts.js"; +import { getSettings } from "~/utils/parseSettings.js"; +import { runExecCommand } from "./installDependencies.js"; + +export async function shadcnInstall(components: string | string[]) { + const componentsArray = Array.isArray(components) ? components : [components]; + const command = ["shadcn@latest", "add", ...componentsArray]; + await runExecCommand({ + command, + loadingMessage: "Installing components...", + successMessage: "Components installed successfully!", + }); +} + +export function getRegistryUrl(): string { + const url = getSettings().registryUrl ?? DEFAULT_REGISTRY_URL; + return url.endsWith("/") ? url.slice(0, -1) : url; +} diff --git a/packages/cli/src/installers/dependencyVersionMap.ts b/packages/cli/src/installers/dependencyVersionMap.ts index 13df7f69..59edbdd3 100644 --- a/packages/cli/src/installers/dependencyVersionMap.ts +++ b/packages/cli/src/installers/dependencyVersionMap.ts @@ -88,5 +88,16 @@ export const dependencyVersionMap = { // Icons (for shadcn/ui) "lucide-react": "^0.518.0", + + // Mantine UI + "@mantine/core": "^7.15.0", + "@mantine/dates": "^7.15.0", + "@mantine/hooks": "^7.15.0", + "@mantine/modals": "^7.15.0", + "@mantine/notifications": "^7.15.0", + "mantine-react-table": "^2.0.0", + + // Theme utilities + "next-themes": "^0.4.6", } as const; export type AvailableDependencies = keyof typeof dependencyVersionMap; diff --git a/packages/cli/src/state.ts b/packages/cli/src/state.ts index e4c8ac78..ec90cc03 100644 --- a/packages/cli/src/state.ts +++ b/packages/cli/src/state.ts @@ -10,6 +10,7 @@ const schema = z .optional() .catch(undefined), appType: z.enum(["browser", "webviewer"]).optional().catch(undefined), + ui: z.enum(["shadcn", "mantine"]).optional().catch("mantine"), projectDir: z.string().default(process.cwd()), authType: z.enum(["clerk", "fmaddon"]).optional(), emailProvider: z.enum(["plunk", "resend", "none"]).optional(), diff --git a/packages/cli/src/utils/addPackageDependency.ts b/packages/cli/src/utils/addPackageDependency.ts index 63c3bcbf..461599f7 100644 --- a/packages/cli/src/utils/addPackageDependency.ts +++ b/packages/cli/src/utils/addPackageDependency.ts @@ -7,13 +7,14 @@ import { dependencyVersionMap, type AvailableDependencies, } from "~/installers/dependencyVersionMap.js"; +import { state } from "~/state.js"; export const addPackageDependency = (opts: { dependencies: AvailableDependencies[]; devMode: boolean; - projectDir: string; + projectDir?: string; }) => { - const { dependencies, devMode, projectDir } = opts; + const { dependencies, devMode, projectDir = state.projectDir } = opts; const pkgJson = fs.readJSONSync( path.join(projectDir, "package.json") diff --git a/packages/cli/src/utils/parseSettings.ts b/packages/cli/src/utils/parseSettings.ts index 5bd0f4a0..b999fcc6 100644 --- a/packages/cli/src/utils/parseSettings.ts +++ b/packages/cli/src/utils/parseSettings.ts @@ -2,6 +2,7 @@ import path from "path"; import fs from "fs-extra"; import { z } from "zod/v4"; +import { DEFAULT_REGISTRY_URL } from "~/consts.js"; import { state } from "~/state.js"; const authSchema = z @@ -44,14 +45,19 @@ export type DataSource = z.infer; export const appTypes = ["browser", "webviewer"] as const; +export const uiTypes = ["shadcn", "mantine"] as const; +export type Ui = (typeof uiTypes)[number]; + const settingsSchema = z.object({ appType: z.enum(appTypes).default("browser"), + ui: z.enum(uiTypes).default("mantine"), // default if the ui key is missing auth: authSchema, envFile: z.string().default(".env"), dataSources: z.array(dataSourceSchema).default([]), tanstackQuery: z.boolean().catch(false), replacedMainPage: z.boolean().catch(false), appliedUpgrades: z.array(z.string()).default([]), + registryUrl: z.url().optional(), }); export const defaultSettings = settingsSchema.parse({ auth: { type: "none" } }); @@ -60,14 +66,13 @@ let settings: Settings | undefined; export const getSettings = () => { if (settings) return settings; - const settingsFile: unknown = fs.readJSONSync( - path.join(state.projectDir, "proofkit.json") - ); + const settingsPath = path.join(state.projectDir, "proofkit.json"); + const settingsFile: unknown = fs.readJSONSync(settingsPath); const parsed = settingsSchema.parse(settingsFile); - settings = parsed; + state.appType = parsed.appType; - return settings; + return parsed; }; export type Settings = z.infer; diff --git a/packages/cli/template/nextjs/.prettierrc b/packages/cli/template/nextjs-mantine/.prettierrc similarity index 100% rename from packages/cli/template/nextjs/.prettierrc rename to packages/cli/template/nextjs-mantine/.prettierrc diff --git a/packages/cli/template/nextjs/README.md b/packages/cli/template/nextjs-mantine/README.md similarity index 100% rename from packages/cli/template/nextjs/README.md rename to packages/cli/template/nextjs-mantine/README.md diff --git a/packages/cli/template/nextjs/_gitignore b/packages/cli/template/nextjs-mantine/_gitignore similarity index 100% rename from packages/cli/template/nextjs/_gitignore rename to packages/cli/template/nextjs-mantine/_gitignore diff --git a/packages/cli/template/nextjs/components.json b/packages/cli/template/nextjs-mantine/components.json similarity index 100% rename from packages/cli/template/nextjs/components.json rename to packages/cli/template/nextjs-mantine/components.json diff --git a/packages/cli/template/nextjs/next.config.ts b/packages/cli/template/nextjs-mantine/next.config.ts similarity index 100% rename from packages/cli/template/nextjs/next.config.ts rename to packages/cli/template/nextjs-mantine/next.config.ts diff --git a/packages/cli/template/nextjs/package.json b/packages/cli/template/nextjs-mantine/package.json similarity index 100% rename from packages/cli/template/nextjs/package.json rename to packages/cli/template/nextjs-mantine/package.json diff --git a/packages/cli/template/nextjs/postcss.config.cjs b/packages/cli/template/nextjs-mantine/postcss.config.cjs similarity index 100% rename from packages/cli/template/nextjs/postcss.config.cjs rename to packages/cli/template/nextjs-mantine/postcss.config.cjs diff --git a/packages/cli/template/nextjs-mantine/proofkit.json b/packages/cli/template/nextjs-mantine/proofkit.json new file mode 100644 index 00000000..c536f9bf --- /dev/null +++ b/packages/cli/template/nextjs-mantine/proofkit.json @@ -0,0 +1,7 @@ +{ + "auth": { "type": "none" }, + "envFile": ".env", + "appType": "browser", + "ui": "mantine", + "appliedUpgrades": ["cursorRules"] +} diff --git a/packages/cli/template/nextjs/public/favicon.ico b/packages/cli/template/nextjs-mantine/public/favicon.ico similarity index 100% rename from packages/cli/template/nextjs/public/favicon.ico rename to packages/cli/template/nextjs-mantine/public/favicon.ico diff --git a/packages/cli/template/nextjs/public/proofkit.png b/packages/cli/template/nextjs-mantine/public/proofkit.png similarity index 100% rename from packages/cli/template/nextjs/public/proofkit.png rename to packages/cli/template/nextjs-mantine/public/proofkit.png diff --git a/packages/cli/template/nextjs/src/app/(main)/layout.tsx b/packages/cli/template/nextjs-mantine/src/app/(main)/layout.tsx similarity index 100% rename from packages/cli/template/nextjs/src/app/(main)/layout.tsx rename to packages/cli/template/nextjs-mantine/src/app/(main)/layout.tsx diff --git a/packages/cli/template/nextjs/src/app/(main)/page.tsx b/packages/cli/template/nextjs-mantine/src/app/(main)/page.tsx similarity index 100% rename from packages/cli/template/nextjs/src/app/(main)/page.tsx rename to packages/cli/template/nextjs-mantine/src/app/(main)/page.tsx diff --git a/packages/cli/template/nextjs/src/app/layout.tsx b/packages/cli/template/nextjs-mantine/src/app/layout.tsx similarity index 100% rename from packages/cli/template/nextjs/src/app/layout.tsx rename to packages/cli/template/nextjs-mantine/src/app/layout.tsx diff --git a/packages/cli/template/nextjs/src/app/navigation.tsx b/packages/cli/template/nextjs-mantine/src/app/navigation.tsx similarity index 100% rename from packages/cli/template/nextjs/src/app/navigation.tsx rename to packages/cli/template/nextjs-mantine/src/app/navigation.tsx diff --git a/packages/cli/template/nextjs/src/components/AppLogo.tsx b/packages/cli/template/nextjs-mantine/src/components/AppLogo.tsx similarity index 100% rename from packages/cli/template/nextjs/src/components/AppLogo.tsx rename to packages/cli/template/nextjs-mantine/src/components/AppLogo.tsx diff --git a/packages/cli/template/nextjs/src/components/AppShell/internal/AppShell.tsx b/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/AppShell.tsx similarity index 100% rename from packages/cli/template/nextjs/src/components/AppShell/internal/AppShell.tsx rename to packages/cli/template/nextjs-mantine/src/components/AppShell/internal/AppShell.tsx diff --git a/packages/cli/template/nextjs/src/components/AppShell/internal/Header.module.css b/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/Header.module.css similarity index 100% rename from packages/cli/template/nextjs/src/components/AppShell/internal/Header.module.css rename to packages/cli/template/nextjs-mantine/src/components/AppShell/internal/Header.module.css diff --git a/packages/cli/template/nextjs/src/components/AppShell/internal/Header.tsx b/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/Header.tsx similarity index 100% rename from packages/cli/template/nextjs/src/components/AppShell/internal/Header.tsx rename to packages/cli/template/nextjs-mantine/src/components/AppShell/internal/Header.tsx diff --git a/packages/cli/template/nextjs/src/components/AppShell/internal/HeaderMobileMenu.tsx b/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/HeaderMobileMenu.tsx similarity index 100% rename from packages/cli/template/nextjs/src/components/AppShell/internal/HeaderMobileMenu.tsx rename to packages/cli/template/nextjs-mantine/src/components/AppShell/internal/HeaderMobileMenu.tsx diff --git a/packages/cli/template/nextjs/src/components/AppShell/internal/HeaderNavLink.tsx b/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/HeaderNavLink.tsx similarity index 100% rename from packages/cli/template/nextjs/src/components/AppShell/internal/HeaderNavLink.tsx rename to packages/cli/template/nextjs-mantine/src/components/AppShell/internal/HeaderNavLink.tsx diff --git a/packages/cli/template/nextjs/src/components/AppShell/internal/config.ts b/packages/cli/template/nextjs-mantine/src/components/AppShell/internal/config.ts similarity index 100% rename from packages/cli/template/nextjs/src/components/AppShell/internal/config.ts rename to packages/cli/template/nextjs-mantine/src/components/AppShell/internal/config.ts diff --git a/packages/cli/template/nextjs/src/components/AppShell/slot-header-center.tsx b/packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-center.tsx similarity index 100% rename from packages/cli/template/nextjs/src/components/AppShell/slot-header-center.tsx rename to packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-center.tsx diff --git a/packages/cli/template/nextjs/src/components/AppShell/slot-header-left.tsx b/packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-left.tsx similarity index 100% rename from packages/cli/template/nextjs/src/components/AppShell/slot-header-left.tsx rename to packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-left.tsx diff --git a/packages/cli/template/nextjs/src/components/AppShell/slot-header-mobile-content.tsx b/packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-mobile-content.tsx similarity index 100% rename from packages/cli/template/nextjs/src/components/AppShell/slot-header-mobile-content.tsx rename to packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-mobile-content.tsx diff --git a/packages/cli/template/nextjs/src/components/AppShell/slot-header-right.tsx b/packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-right.tsx similarity index 100% rename from packages/cli/template/nextjs/src/components/AppShell/slot-header-right.tsx rename to packages/cli/template/nextjs-mantine/src/components/AppShell/slot-header-right.tsx diff --git a/packages/cli/template/nextjs/src/config/env.ts b/packages/cli/template/nextjs-mantine/src/config/env.ts similarity index 100% rename from packages/cli/template/nextjs/src/config/env.ts rename to packages/cli/template/nextjs-mantine/src/config/env.ts diff --git a/packages/cli/template/nextjs/src/config/theme/globals.css b/packages/cli/template/nextjs-mantine/src/config/theme/globals.css similarity index 100% rename from packages/cli/template/nextjs/src/config/theme/globals.css rename to packages/cli/template/nextjs-mantine/src/config/theme/globals.css diff --git a/packages/cli/template/nextjs/src/config/theme/mantine-theme.ts b/packages/cli/template/nextjs-mantine/src/config/theme/mantine-theme.ts similarity index 100% rename from packages/cli/template/nextjs/src/config/theme/mantine-theme.ts rename to packages/cli/template/nextjs-mantine/src/config/theme/mantine-theme.ts diff --git a/packages/cli/template/nextjs/src/server/safe-action.ts b/packages/cli/template/nextjs-mantine/src/server/safe-action.ts similarity index 100% rename from packages/cli/template/nextjs/src/server/safe-action.ts rename to packages/cli/template/nextjs-mantine/src/server/safe-action.ts diff --git a/packages/cli/template/nextjs/src/utils/notification-helpers.ts b/packages/cli/template/nextjs-mantine/src/utils/notification-helpers.ts similarity index 100% rename from packages/cli/template/nextjs/src/utils/notification-helpers.ts rename to packages/cli/template/nextjs-mantine/src/utils/notification-helpers.ts diff --git a/packages/cli/template/nextjs/src/utils/styles.ts b/packages/cli/template/nextjs-mantine/src/utils/styles.ts similarity index 100% rename from packages/cli/template/nextjs/src/utils/styles.ts rename to packages/cli/template/nextjs-mantine/src/utils/styles.ts diff --git a/packages/cli/template/nextjs/tsconfig.json b/packages/cli/template/nextjs-mantine/tsconfig.json similarity index 100% rename from packages/cli/template/nextjs/tsconfig.json rename to packages/cli/template/nextjs-mantine/tsconfig.json diff --git a/packages/cli/template/nextjs-shadcn/.prettierrc b/packages/cli/template/nextjs-shadcn/.prettierrc new file mode 100644 index 00000000..b4bfed35 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/.prettierrc @@ -0,0 +1,3 @@ +{ + "plugins": ["prettier-plugin-tailwindcss"] +} diff --git a/packages/cli/template/nextjs-shadcn/README.md b/packages/cli/template/nextjs-shadcn/README.md new file mode 100644 index 00000000..4f416a4a --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/README.md @@ -0,0 +1,27 @@ +# ProofKit NextJS Template + +This is a [NextJS](https://nextjs.org/) project bootstrapped with `@proofkit/cli`. Learn more at [proofkit.dev](https://proofkit.dev) + +## What's next? How do I make an app with this? + +While this template is designed to be a minimal starting point, the proofkit CLI will guide you through adding additional features and pages. + +To add new things to your project, simply run the `proofkit` script from the project's root directory. + +e.g. `npm run proofkit` or `pnpm proofkit` etc. + +For more information, see the full [ProofKit documentation](https://proofkit.dev). + +## Project Structure + +ProofKit projects have an opinionated structure to help you get started and some conventions must be maintained to ensure that the CLI can properly inject new features and components. + +The `src` directory is the home for your application code. It is used for most things except for configuration and is organized as follows: + +- `app` - NextJS app router, where your pages and routes are defined +- `components` - Shared components used throughout the app +- `server` - Code that connects to backend databases and services that should not be exposed in the browser + +Anytime you see an `internal` folder, you should not modify any files inside. These files are maintained exclusively by the ProofKit CLI and changes to them may be overwritten. + +Anytime you see a componet file that begins with `slot-`, you _may_ modify the content, but do not rename, remove, or move them. These are desigend to be customized, but are still used by the CLI to inject additional content. If a slot is not needed by your app, you can have the compoment return `null` or an empty fragment: `<>` diff --git a/packages/cli/template/nextjs-shadcn/_gitignore b/packages/cli/template/nextjs-shadcn/_gitignore new file mode 100644 index 00000000..00bba9bb --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/_gitignore @@ -0,0 +1,37 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js +.yarn/install-state.gz + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local +.env + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/packages/cli/template/nextjs-shadcn/components.json b/packages/cli/template/nextjs-shadcn/components.json new file mode 100644 index 00000000..7cccc384 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "new-york", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/config/theme/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/utils/styles", + "ui": "@/components/ui", + "lib": "@/utils", + "hooks": "@/utils/hooks" + }, + "iconLibrary": "lucide" +} diff --git a/packages/cli/template/nextjs-shadcn/next.config.ts b/packages/cli/template/nextjs-shadcn/next.config.ts new file mode 100644 index 00000000..5a07c277 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/next.config.ts @@ -0,0 +1,8 @@ +import { type NextConfig } from "next"; + +// Import env here to validate during build. +import "./src/config/env"; + +const nextConfig: NextConfig = {}; + +export default nextConfig; diff --git a/packages/cli/template/nextjs-shadcn/package.json b/packages/cli/template/nextjs-shadcn/package.json new file mode 100644 index 00000000..5c9833a7 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/package.json @@ -0,0 +1,42 @@ +{ + "name": "template", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev --turbopack", + "build": "next build", + "start": "next start", + "lint": "next lint", + "proofkit": "proofkit", + "typegen": "proofkit typegen", + "deploy": "proofkit deploy" + }, + "dependencies": { + "@hookform/resolvers": "^5.1.1", + "@next-safe-action/adapter-react-hook-form": "^2.0.0", + "next-safe-action": "^8.0.4", + "react-hook-form": "^7.54.2", + "@tabler/icons-react": "^3.30.0", + "@t3-oss/env-nextjs": "^0.12.0", + "dayjs": "^1.11.13", + "next": "^15.2.3", + "react": "19.0.0", + "react-dom": "19.0.0", + "zod": "^3.24.2" + }, + "devDependencies": { + "@types/node": "^20", + "@types/react": "npm:types-react@19.0.12", + "@types/react-dom": "npm:types-react-dom@19.0.4", + "eslint": "^9.14.0", + "eslint-config-next": "15.2.3", + "postcss": "^8.4.41", + "typescript": "^5" + }, + "pnpm": { + "overrides": { + "@types/react": "npm:types-react@19.0.0-rc.1", + "@types/react-dom": "npm:types-react-dom@19.0.0-rc.1" + } + } +} diff --git a/packages/cli/template/nextjs-shadcn/postcss.config.cjs b/packages/cli/template/nextjs-shadcn/postcss.config.cjs new file mode 100644 index 00000000..483f3785 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/postcss.config.cjs @@ -0,0 +1,5 @@ +module.exports = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; diff --git a/packages/cli/template/nextjs/proofkit.json b/packages/cli/template/nextjs-shadcn/proofkit.json similarity index 87% rename from packages/cli/template/nextjs/proofkit.json rename to packages/cli/template/nextjs-shadcn/proofkit.json index daca7e48..6aca1027 100644 --- a/packages/cli/template/nextjs/proofkit.json +++ b/packages/cli/template/nextjs-shadcn/proofkit.json @@ -2,5 +2,6 @@ "auth": { "type": "none" }, "envFile": ".env", "appType": "browser", + "ui": "shadcn", "appliedUpgrades": ["shadcn", "cursorRules"] } diff --git a/packages/cli/template/nextjs-shadcn/public/favicon.ico b/packages/cli/template/nextjs-shadcn/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ba9355b8d3f888ad3a93c0f254b6776a0c2aed92 GIT binary patch literal 15086 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 DtPcMKfsh_-A)LFM)6iMLm_0IRoy0b^RZ#+}< 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 b29HP@D9EFJDBd}ZfIn(VT8gjb%^v(OKI6J9 literal 0 HcmV?d00001 diff --git a/packages/cli/template/nextjs-shadcn/src/app/(main)/layout.tsx b/packages/cli/template/nextjs-shadcn/src/app/(main)/layout.tsx new file mode 100644 index 00000000..57a8452b --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/app/(main)/layout.tsx @@ -0,0 +1,6 @@ +import AppShell from "@/components/AppShell/internal/AppShell"; +import React from "react"; + +export default function Layout({ children }: { children: React.ReactNode }) { + return {children}; +} diff --git a/packages/cli/template/nextjs-shadcn/src/app/(main)/page.tsx b/packages/cli/template/nextjs-shadcn/src/app/(main)/page.tsx new file mode 100644 index 00000000..5c9443b3 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/app/(main)/page.tsx @@ -0,0 +1,116 @@ +"use client"; + +import { + IconBrandGithub, + IconExternalLink, + IconCopy, + IconCheck, + IconTerminal, +} from "@tabler/icons-react"; +import { Button } from "@/components/ui/button"; +import { useState } from "react"; + +function InlineSnippet({ command }: { command: string }) { + const [copied, setCopied] = useState(false); + + const onCopy = () => { + if (typeof window === "undefined" || !navigator.clipboard?.writeText) + return; + navigator.clipboard.writeText(command).then( + () => { + setCopied(true); + setTimeout(() => setCopied(false), 2000); + }, + () => {}, + ); + }; + + return ( +
+
+ +
+ + {command} + +
+ +
+
+ ); +} + +export default function Home() { + return ( +
+
+
+ ProofKit +

Welcome!

+ +

+ This is the base template home page. To add more pages, components, + or other features, run the ProofKit CLI from within your project. +

+ + + +

+ To change this page, open src/app/(main)/page.tsx +

+ +
+
+
+
+
+ Sponsored by{" "} + + Proof+Geist + {" "} + and{" "} + + Ottomatic + +
+
+ + + +
+
+
+
+ ); +} diff --git a/packages/cli/template/nextjs-shadcn/src/app/layout.tsx b/packages/cli/template/nextjs-shadcn/src/app/layout.tsx new file mode 100644 index 00000000..70a51fc1 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/app/layout.tsx @@ -0,0 +1,38 @@ +import { type Metadata } from "next"; +import "@/config/theme/globals.css"; + +export const metadata: Metadata = { + title: "My ProofKit App", + description: "Generated by proofkit", + icons: [{ rel: "icon", url: "/favicon.ico" }], +}; + +import { Geist, Geist_Mono } from "next/font/google"; + +import Providers from "@/components/providers"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/packages/cli/template/nextjs-shadcn/src/app/navigation.tsx b/packages/cli/template/nextjs-shadcn/src/app/navigation.tsx new file mode 100644 index 00000000..887073db --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/app/navigation.tsx @@ -0,0 +1,12 @@ +import { type ProofKitRoute } from "@proofkit/cli"; + +export const primaryRoutes: ProofKitRoute[] = [ + { + label: "Dashboard", + type: "link", + href: "/", + exactMatch: true, + }, +]; + +export const secondaryRoutes: ProofKitRoute[] = []; diff --git a/packages/cli/template/nextjs-shadcn/src/components/AppLogo.tsx b/packages/cli/template/nextjs-shadcn/src/components/AppLogo.tsx new file mode 100644 index 00000000..f5ea4966 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/components/AppLogo.tsx @@ -0,0 +1,6 @@ +import { IconInfinity } from "@tabler/icons-react"; +import React from "react"; + +export default function AppLogo() { + return ; +} diff --git a/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/AppShell.tsx b/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/AppShell.tsx new file mode 100644 index 00000000..33ccb2df --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/AppShell.tsx @@ -0,0 +1,23 @@ +import React from "react"; +import { Header } from "@/components/AppShell/internal/Header"; +import { headerHeight } from "./config"; + +export default function MainAppShell({ + children, +}: { + children: React.ReactNode; +}) { + return ( +
+
+
+
+
+ {children} +
+
+ ); +} diff --git a/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/Header.module.css b/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/Header.module.css new file mode 100644 index 00000000..2733308e --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/Header.module.css @@ -0,0 +1,33 @@ +.header { + margin-bottom: 7.5rem; + background-color: var(--pk-header-bg, transparent); + border-bottom: 1px solid var(--pk-border, rgba(0,0,0,0.08)); +} + +.inner { + display: flex; + justify-content: space-between; + align-items: center; +} + +.link { + display: block; + line-height: 1; + padding: 0.5rem 0.75rem; + border-radius: 0.375rem; + text-decoration: none; + color: inherit; + font-size: 0.875rem; + font-weight: 500; + cursor: pointer; + background: none; + border: none; +} + +.link:hover { + background-color: rgba(0, 0, 0, 0.05); +} + +[data-theme="dark"] .link:hover { + background-color: rgba(255, 255, 255, 0.06); +} diff --git a/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/Header.tsx b/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/Header.tsx new file mode 100644 index 00000000..a302ecce --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/Header.tsx @@ -0,0 +1,30 @@ +import SlotHeaderCenter from "../slot-header-center"; +import SlotHeaderLeft from "../slot-header-left"; +import SlotHeaderRight from "../slot-header-right"; +import { headerHeight } from "./config"; +import classes from "./Header.module.css"; +import HeaderMobileMenu from "./HeaderMobileMenu"; + +export function Header() { + return ( +
+
+
+ +
+ +
+
+ +
+
+ +
+
+
+
+ ); +} diff --git a/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/HeaderMobileMenu.tsx b/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/HeaderMobileMenu.tsx new file mode 100644 index 00000000..ac2a2e2b --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/HeaderMobileMenu.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { useState } from "react"; +import SlotHeaderMobileMenuContent from "../slot-header-mobile-content"; + +export default function HeaderMobileMenu() { + const [opened, setOpened] = useState(false); + + return ( +
+ + {opened && ( +
+ setOpened(false)} /> +
+ )} +
+ ); +} diff --git a/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/HeaderNavLink.tsx b/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/HeaderNavLink.tsx new file mode 100644 index 00000000..5da52246 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/HeaderNavLink.tsx @@ -0,0 +1,31 @@ +"use client"; + +import { type ProofKitRoute } from "@proofkit/cli"; +import { usePathname } from "next/navigation"; +import React from "react"; + +import classes from "./Header.module.css"; + +export default function HeaderNavLink(route: ProofKitRoute) { + const pathname = usePathname(); + + if (route.type === "function") { + return ; + } + + const isActive = route.exactMatch + ? pathname === route.href + : pathname.startsWith(route.href); + + if (route.type === "link") { + return ( + + {route.label} + + ); + } +} diff --git a/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/config.ts b/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/config.ts new file mode 100644 index 00000000..ded639d0 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/components/AppShell/internal/config.ts @@ -0,0 +1 @@ +export const headerHeight = 56; diff --git a/packages/cli/template/nextjs-shadcn/src/components/AppShell/slot-header-center.tsx b/packages/cli/template/nextjs-shadcn/src/components/AppShell/slot-header-center.tsx new file mode 100644 index 00000000..2de3b630 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/components/AppShell/slot-header-center.tsx @@ -0,0 +1,13 @@ +/** + * DO NOT REMOVE / RENAME THIS FILE + * + * You may CUSTOMIZE the content of this file, but the ProofKit CLI expects + * this file to exist and may use it to inject content for other components. + * + * If you don't want it to be used, you may return null or an empty fragment + */ +export function SlotHeaderCenter() { + return null; +} + +export default SlotHeaderCenter; diff --git a/packages/cli/template/nextjs-shadcn/src/components/AppShell/slot-header-left.tsx b/packages/cli/template/nextjs-shadcn/src/components/AppShell/slot-header-left.tsx new file mode 100644 index 00000000..781fcbce --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/components/AppShell/slot-header-left.tsx @@ -0,0 +1,23 @@ +import Link from "next/link"; + +import AppLogo from "../AppLogo"; + +/** + * DO NOT REMOVE / RENAME THIS FILE + * + * You may CUSTOMIZE the content of this file, but the ProofKit CLI expects this file to exist and + * may use it to inject content for other components. + * + * If you don't want it to be used, you may return null or an empty fragment + */ +export function SlotHeaderLeft() { + return ( + <> + + + + + ); +} + +export default SlotHeaderLeft; diff --git a/packages/cli/template/nextjs-shadcn/src/components/AppShell/slot-header-mobile-content.tsx b/packages/cli/template/nextjs-shadcn/src/components/AppShell/slot-header-mobile-content.tsx new file mode 100644 index 00000000..f63d0365 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/components/AppShell/slot-header-mobile-content.tsx @@ -0,0 +1,43 @@ +"use client"; + +import { primaryRoutes } from "@/app/navigation"; +import { useRouter } from "next/navigation"; + +/** + * DO NOT REMOVE / RENAME THIS FILE + * + * You may CUSTOMIZE the content of this file, but the ProofKit CLI expects + * this file to exist and may use it to inject content for other components. + * + * If you don't want it to be used, you may return null or an empty fragment + */ +export function SlotHeaderMobileMenuContent({ + closeMenu, +}: { + closeMenu: () => void; +}) { + const router = useRouter(); + return ( +
+ {primaryRoutes.map((route) => ( + + ))} +
+ ); +} + +export default SlotHeaderMobileMenuContent; diff --git a/packages/cli/template/nextjs-shadcn/src/components/AppShell/slot-header-right.tsx b/packages/cli/template/nextjs-shadcn/src/components/AppShell/slot-header-right.tsx new file mode 100644 index 00000000..afe06352 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/components/AppShell/slot-header-right.tsx @@ -0,0 +1,25 @@ +import { primaryRoutes } from "@/app/navigation"; + +import HeaderNavLink from "./internal/HeaderNavLink"; +import { ModeToggle } from "../mode-toggle"; + +/** + * DO NOT REMOVE / RENAME THIS FILE + * + * You may CUSTOMIZE the content of this file, but the ProofKit CLI expects + * this file to exist and may use it to inject content for other components. + * + * If you don't want it to be used, you may return null or an empty fragment + */ +export function SlotHeaderRight() { + return ( +
+ {primaryRoutes.map((route) => ( + + ))} + +
+ ); +} + +export default SlotHeaderRight; diff --git a/packages/cli/template/nextjs-shadcn/src/components/providers.tsx b/packages/cli/template/nextjs-shadcn/src/components/providers.tsx new file mode 100644 index 00000000..a101d447 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/components/providers.tsx @@ -0,0 +1,13 @@ +"use client"; + +import { ThemeProvider } from "./theme-provider"; +import { Toaster } from "./ui/sonner"; + +export default function Providers({ children }: { children: React.ReactNode }) { + return ( + + {children} + + + ); +} diff --git a/packages/cli/template/nextjs-shadcn/src/components/theme-provider.tsx b/packages/cli/template/nextjs-shadcn/src/components/theme-provider.tsx new file mode 100644 index 00000000..6459132f --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/components/theme-provider.tsx @@ -0,0 +1,11 @@ +"use client"; + +import { ThemeProvider as NextThemesProvider } from "next-themes"; +import type * as React from "react"; + +export function ThemeProvider({ + children, + ...props +}: React.ComponentProps) { + return {children}; +} diff --git a/packages/cli/template/nextjs-shadcn/src/config/env.ts b/packages/cli/template/nextjs-shadcn/src/config/env.ts new file mode 100644 index 00000000..3c50ef8d --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/config/env.ts @@ -0,0 +1,13 @@ +import { createEnv } from "@t3-oss/env-nextjs"; +import { z } from "zod/v4"; + +export const env = createEnv({ + server: { + NODE_ENV: z + .enum(["development", "test", "production"]) + .default("development"), + }, + client: {}, + // For Next.js >= 13.4.4, you only need to destructure client variables: + experimental__runtimeEnv: {}, +}); diff --git a/packages/cli/template/nextjs-shadcn/src/config/theme/globals.css b/packages/cli/template/nextjs-shadcn/src/config/theme/globals.css new file mode 100644 index 00000000..5a886a5a --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/config/theme/globals.css @@ -0,0 +1,126 @@ +/* Add global styles here */ + +@import "tailwindcss"; +@import "tw-animate-css"; + +@theme { + --font-sans: + "Inter", "Geist", ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", + "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji"; +} + +:root { + --radius: 0.625rem; + --background: oklch(1 0 0); + --foreground: oklch(0.145 0 0); + --card: oklch(1 0 0); + --card-foreground: oklch(0.145 0 0); + --popover: oklch(1 0 0); + --popover-foreground: oklch(0.145 0 0); + --primary: oklch(0.205 0 0); + --primary-foreground: oklch(0.985 0 0); + --secondary: oklch(0.97 0 0); + --secondary-foreground: oklch(0.205 0 0); + --muted: oklch(0.97 0 0); + --muted-foreground: oklch(0.556 0 0); + --accent: oklch(0.97 0 0); + --accent-foreground: oklch(0.205 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.922 0 0); + --input: oklch(0.922 0 0); + --ring: oklch(0.708 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.985 0 0); + --sidebar-foreground: oklch(0.145 0 0); + --sidebar-primary: oklch(0.205 0 0); + --sidebar-primary-foreground: oklch(0.985 0 0); + --sidebar-accent: oklch(0.97 0 0); + --sidebar-accent-foreground: oklch(0.205 0 0); + --sidebar-border: oklch(0.922 0 0); + --sidebar-ring: oklch(0.708 0 0); +} + +.dark { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --card: oklch(0.145 0 0); + --card-foreground: oklch(0.985 0 0); + --popover: oklch(0.145 0 0); + --popover-foreground: oklch(0.985 0 0); + --primary: oklch(0.85 0 0); + --primary-foreground: oklch(0.205 0 0); + --secondary: oklch(0.2 0 0); + --secondary-foreground: oklch(0.9 0 0); + --muted: oklch(0.2 0 0); + --muted-foreground: oklch(0.78 0 0); + --accent: oklch(0.22 0 0); + --accent-foreground: oklch(0.9 0 0); + --destructive: oklch(0.577 0.245 27.325); + --border: oklch(0.27 0 0); + --input: oklch(0.27 0 0); + --ring: oklch(0.52 0 0); + --chart-1: oklch(0.646 0.222 41.116); + --chart-2: oklch(0.6 0.118 184.704); + --chart-3: oklch(0.398 0.07 227.392); + --chart-4: oklch(0.828 0.189 84.429); + --chart-5: oklch(0.769 0.188 70.08); + --sidebar: oklch(0.18 0 0); + --sidebar-foreground: oklch(0.97 0 0); + --sidebar-primary: var(--primary); + --sidebar-primary-foreground: var(--primary-foreground); + --sidebar-accent: oklch(0.22 0 0); + --sidebar-accent-foreground: oklch(0.9 0 0); + --sidebar-border: oklch(0.27 0 0); + --sidebar-ring: oklch(0.52 0 0); +} + +@theme inline { + --radius-sm: calc(var(--radius) - 4px); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + --color-accent: var(--accent); + --color-accent-foreground: var(--accent-foreground); + --color-destructive: var(--destructive); + --color-border: var(--border); + --color-input: var(--input); + --color-ring: var(--ring); + --color-chart-1: var(--chart-1); + --color-chart-2: var(--chart-2); + --color-chart-3: var(--chart-3); + --color-chart-4: var(--chart-4); + --color-chart-5: var(--chart-5); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + --color-sidebar-primary: var(--sidebar-primary); + --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); + --color-sidebar-accent: var(--sidebar-accent); + --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); + --color-sidebar-border: var(--sidebar-border); + --color-sidebar-ring: var(--sidebar-ring); +} + +@layer base { + * { + @apply border-border outline-ring/50; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/packages/cli/template/nextjs-shadcn/src/server/safe-action.ts b/packages/cli/template/nextjs-shadcn/src/server/safe-action.ts new file mode 100644 index 00000000..7f62198a --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/server/safe-action.ts @@ -0,0 +1,3 @@ +import { createSafeActionClient } from "next-safe-action"; + +export const actionClient = createSafeActionClient(); diff --git a/packages/cli/template/nextjs-shadcn/src/utils/notification-helpers.ts b/packages/cli/template/nextjs-shadcn/src/utils/notification-helpers.ts new file mode 100644 index 00000000..dc0dd7c8 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/utils/notification-helpers.ts @@ -0,0 +1,15 @@ +export function showErrorNotification(): void; +export function showErrorNotification(message: string): void; +export function showErrorNotification(args?: string): void { + const message = typeof args === "string" ? args : "An unexpected error occurred."; + // TODO: Replace with your preferred toast library + if (typeof window !== "undefined") console.error(message); +} + +export function showSuccessNotification(): void; +export function showSuccessNotification(message: string): void; +export function showSuccessNotification(args?: string): void { + const message = typeof args === "string" ? args : "Success!"; + // TODO: Replace with your preferred toast library + if (typeof window !== "undefined") console.log(message); +} diff --git a/packages/cli/template/nextjs-shadcn/src/utils/styles.ts b/packages/cli/template/nextjs-shadcn/src/utils/styles.ts new file mode 100644 index 00000000..1f4cd38e --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/src/utils/styles.ts @@ -0,0 +1,6 @@ +import { clsx } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs: any[]) { + return twMerge(clsx(inputs)); +} diff --git a/packages/cli/template/nextjs-shadcn/tsconfig.json b/packages/cli/template/nextjs-shadcn/tsconfig.json new file mode 100644 index 00000000..f48e7ee6 --- /dev/null +++ b/packages/cli/template/nextjs-shadcn/tsconfig.json @@ -0,0 +1,40 @@ +{ + "compilerOptions": { + "lib": [ + "dom", + "dom.iterable", + "esnext" + ], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": [ + "./src/*" + ] + }, + "target": "ES2017" + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] +} diff --git a/packages/cli/tsup.config.ts b/packages/cli/tsup.config.ts index a7c6d53d..35e7f4bc 100644 --- a/packages/cli/tsup.config.ts +++ b/packages/cli/tsup.config.ts @@ -8,6 +8,7 @@ export default defineConfig({ format: ["esm"], minify: !isDev, target: "esnext", + replaceNodeEnv: true, outDir: "dist", - onSuccess: isDev ? "node dist/index.js" : undefined, + onSuccess: isDev ? "IS_LOCAL_DEV=1 node dist/index.js" : undefined, });