create-better-t-stack 2.46.3 → 2.47.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (38) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/{src-El86OdG5.js → src-Wv9qeyOh.js} +139 -35
  4. package/package.json +3 -2
  5. package/templates/addons/biome/biome.json.hbs +2 -1
  6. package/templates/addons/ultracite/biome.json.hbs +1 -0
  7. package/templates/auth/better-auth/convex/backend/convex/auth.config.ts.hbs +8 -0
  8. package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +48 -0
  9. package/templates/auth/better-auth/convex/backend/convex/convex.config.ts.hbs +7 -0
  10. package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +12 -0
  11. package/templates/auth/better-auth/convex/backend/convex/privateData.ts.hbs +16 -0
  12. package/templates/auth/better-auth/convex/web/react/next/src/app/api/auth/[...all]/route.ts.hbs +3 -0
  13. package/templates/auth/better-auth/convex/web/react/next/src/app/dashboard/page.tsx.hbs +40 -0
  14. package/templates/auth/better-auth/convex/web/react/next/src/components/sign-in-form.tsx.hbs +129 -0
  15. package/templates/auth/better-auth/convex/web/react/next/src/components/sign-up-form.tsx.hbs +154 -0
  16. package/templates/auth/better-auth/convex/web/react/next/src/components/user-menu.tsx.hbs +48 -0
  17. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-client.ts.hbs +6 -0
  18. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-server.ts.hbs +6 -0
  19. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/sign-in-form.tsx.hbs +133 -0
  20. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/sign-up-form.tsx.hbs +158 -0
  21. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/user-menu.tsx.hbs +50 -0
  22. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/lib/auth-client.ts.hbs +10 -0
  23. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs +43 -0
  24. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/sign-in-form.tsx.hbs +133 -0
  25. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/sign-up-form.tsx.hbs +158 -0
  26. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/user-menu.tsx.hbs +50 -0
  27. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-client.ts.hbs +6 -0
  28. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-server.ts.hbs +5 -0
  29. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/api/auth/$.ts.hbs +11 -0
  30. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs +43 -0
  31. package/templates/deploy/alchemy/alchemy.run.ts.hbs +14 -0
  32. package/templates/frontend/react/next/src/components/providers.tsx.hbs +8 -0
  33. package/templates/frontend/react/tanstack-router/src/main.tsx.hbs +8 -1
  34. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +47 -0
  35. package/templates/frontend/react/tanstack-start/src/routes/index.tsx.hbs +2 -2
  36. package/templates/frontend/react/web-base/src/components/header.tsx.hbs +2 -2
  37. package/templates/auth/better-auth/web/react/next/src/components/theme-provider.tsx.hbs +0 -11
  38. /package/templates/frontend/react/web-base/src/components/{loader.tsx → loader.tsx.hbs} +0 -0
package/dist/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { createBtsCli } from "./src-El86OdG5.js";
2
+ import { createBtsCli } from "./src-Wv9qeyOh.js";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { builder, createBtsCli, docs, init, router, sponsors } from "./src-El86OdG5.js";
2
+ import { builder, createBtsCli, docs, init, router, sponsors } from "./src-Wv9qeyOh.js";
3
3
 
4
4
  export { builder, createBtsCli, docs, init, router, sponsors };
@@ -122,11 +122,12 @@ const dependencyVersionMap = {
122
122
  "@trpc/tanstack-react-query": "^11.5.0",
123
123
  "@trpc/server": "^11.5.0",
124
124
  "@trpc/client": "^11.5.0",
125
- convex: "^1.25.4",
125
+ convex: "^1.27.0",
126
126
  "@convex-dev/react-query": "^0.0.0-alpha.8",
127
127
  "convex-svelte": "^0.0.11",
128
128
  "convex-nuxt": "0.1.5",
129
129
  "convex-vue": "^0.1.5",
130
+ "@convex-dev/better-auth": "^0.8.4",
130
131
  "@tanstack/svelte-query": "^5.85.3",
131
132
  "@tanstack/svelte-query-devtools": "^5.85.3",
132
133
  "@tanstack/vue-query-devtools": "^5.83.0",
@@ -423,11 +424,10 @@ function validateAddonsAgainstFrontends(addons = [], frontends = [], auth) {
423
424
  if (!isCompatible) exitWithError(`Incompatible addon/frontend combination: ${reason}`);
424
425
  }
425
426
  }
426
- function validatePaymentsCompatibility(payments, auth, backend, frontends = []) {
427
+ function validatePaymentsCompatibility(payments, auth, _backend, frontends = []) {
427
428
  if (!payments || payments === "none") return;
428
429
  if (payments === "polar") {
429
430
  if (!auth || auth === "none" || auth !== "better-auth") exitWithError("Polar payments requires Better Auth. Please use '--auth better-auth' or choose a different payments provider.");
430
- if (backend === "convex") exitWithError("Polar payments is not compatible with Convex backend. Please use a different backend or choose a different payments provider.");
431
431
  const { web } = splitFrontends(frontends);
432
432
  if (web.length === 0 && frontends.length > 0) exitWithError("Polar payments requires a web frontend or no frontend. Please select a web frontend or choose a different payments provider.");
433
433
  }
@@ -611,23 +611,39 @@ async function getApiChoice(Api, frontend, backend) {
611
611
  async function getAuthChoice(auth, hasDatabase, backend, frontend) {
612
612
  if (auth !== void 0) return auth;
613
613
  if (backend === "convex") {
614
- const unsupportedFrontends = frontend?.filter((f) => [
615
- "nuxt",
616
- "svelte",
617
- "solid"
614
+ const supportedBetterAuthFrontends = frontend?.some((f) => [
615
+ "tanstack-router",
616
+ "tanstack-start",
617
+ "next"
618
+ ].includes(f));
619
+ const hasClerkCompatibleFrontends = frontend?.some((f) => [
620
+ "react-router",
621
+ "tanstack-router",
622
+ "tanstack-start",
623
+ "next",
624
+ "native-nativewind",
625
+ "native-unistyles"
618
626
  ].includes(f));
619
- if (unsupportedFrontends && unsupportedFrontends.length > 0) return "none";
627
+ const options = [];
628
+ if (supportedBetterAuthFrontends) options.push({
629
+ value: "better-auth",
630
+ label: "Better-Auth",
631
+ hint: "comprehensive auth framework for TypeScript"
632
+ });
633
+ if (hasClerkCompatibleFrontends) options.push({
634
+ value: "clerk",
635
+ label: "Clerk",
636
+ hint: "More than auth, Complete User Management"
637
+ });
638
+ options.push({
639
+ value: "none",
640
+ label: "None",
641
+ hint: "No auth"
642
+ });
620
643
  const response$1 = await select({
621
644
  message: "Select authentication provider",
622
- options: [{
623
- value: "clerk",
624
- label: "Clerk",
625
- hint: "More than auth, Complete User Management"
626
- }, {
627
- value: "none",
628
- label: "None"
629
- }],
630
- initialValue: "clerk"
645
+ options,
646
+ initialValue: "none"
631
647
  });
632
648
  if (isCancel(response$1)) return exitCancelled("Operation cancelled");
633
649
  return response$1;
@@ -1733,7 +1749,15 @@ function validateConvexConstraints(config, providedFlags) {
1733
1749
  if (has("api") && config.api !== "none") exitWithError("Convex backend requires '--api none'. Please remove the --api flag or set it to 'none'.");
1734
1750
  if (has("dbSetup") && config.dbSetup !== "none") exitWithError("Convex backend requires '--db-setup none'. Please remove the --db-setup flag or set it to 'none'.");
1735
1751
  if (has("serverDeploy") && config.serverDeploy !== "none") exitWithError("Convex backend requires '--server-deploy none'. Please remove the --server-deploy flag or set it to 'none'.");
1736
- if (has("auth") && config.auth === "better-auth") exitWithError("Better-Auth is not compatible with Convex backend. Please use '--auth clerk' or '--auth none'.");
1752
+ if (has("auth") && config.auth === "better-auth") {
1753
+ const supportedFrontends = [
1754
+ "tanstack-router",
1755
+ "tanstack-start",
1756
+ "next"
1757
+ ];
1758
+ const hasSupportedFrontend = config.frontend?.some((f) => supportedFrontends.includes(f));
1759
+ if (!hasSupportedFrontend) exitWithError("Better-Auth with Convex backend is only supported with TanStack Router, TanStack Start, or Next.js frontends. Please use '--auth clerk' or '--auth none'.");
1760
+ }
1737
1761
  }
1738
1762
  function validateBackendNoneConstraints(config, providedFlags) {
1739
1763
  const { backend } = config;
@@ -1980,18 +2004,18 @@ async function updateBtsConfig(projectDir, updates) {
1980
2004
  //#endregion
1981
2005
  //#region src/utils/add-package-deps.ts
1982
2006
  const addPackageDependency = async (opts) => {
1983
- const { dependencies = [], devDependencies = [], projectDir } = opts;
2007
+ const { dependencies = [], devDependencies = [], customDependencies = {}, customDevDependencies = {}, projectDir } = opts;
1984
2008
  const pkgJsonPath = path.join(projectDir, "package.json");
1985
2009
  const pkgJson = await fs.readJson(pkgJsonPath);
1986
2010
  if (!pkgJson.dependencies) pkgJson.dependencies = {};
1987
2011
  if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
1988
2012
  for (const pkgName of dependencies) {
1989
- const version = dependencyVersionMap[pkgName];
2013
+ const version = customDependencies[pkgName] || dependencyVersionMap[pkgName];
1990
2014
  if (version) pkgJson.dependencies[pkgName] = version;
1991
2015
  else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
1992
2016
  }
1993
2017
  for (const pkgName of devDependencies) {
1994
- const version = dependencyVersionMap[pkgName];
2018
+ const version = customDevDependencies[pkgName] || dependencyVersionMap[pkgName];
1995
2019
  if (version) pkgJson.devDependencies[pkgName] = version;
1996
2020
  else console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
1997
2021
  }
@@ -2853,6 +2877,29 @@ async function setupAuthTemplate(projectDir, context) {
2853
2877
  }
2854
2878
  return;
2855
2879
  }
2880
+ if (context.backend === "convex" && authProvider === "better-auth") {
2881
+ const convexBackendDestDir = path.join(projectDir, "packages/backend");
2882
+ const convexBetterAuthBackendSrc = path.join(PKG_ROOT, "templates/auth/better-auth/convex/backend");
2883
+ if (await fs.pathExists(convexBetterAuthBackendSrc)) {
2884
+ await fs.ensureDir(convexBackendDestDir);
2885
+ await processAndCopyFiles("**/*", convexBetterAuthBackendSrc, convexBackendDestDir, context);
2886
+ }
2887
+ if (webAppDirExists && hasReactWeb) {
2888
+ const convexBetterAuthWebBaseSrc = path.join(PKG_ROOT, "templates/auth/better-auth/convex/web/react/base");
2889
+ if (await fs.pathExists(convexBetterAuthWebBaseSrc)) await processAndCopyFiles("**/*", convexBetterAuthWebBaseSrc, webAppDir, context);
2890
+ const reactFramework = context.frontend.find((f) => [
2891
+ "tanstack-router",
2892
+ "react-router",
2893
+ "tanstack-start",
2894
+ "next"
2895
+ ].includes(f));
2896
+ if (reactFramework) {
2897
+ const convexBetterAuthWebSrc = path.join(PKG_ROOT, `templates/auth/better-auth/convex/web/react/${reactFramework}`);
2898
+ if (await fs.pathExists(convexBetterAuthWebSrc)) await processAndCopyFiles("**/*", convexBetterAuthWebSrc, webAppDir, context);
2899
+ }
2900
+ }
2901
+ return;
2902
+ }
2856
2903
  if (serverAppDirExists && context.backend !== "convex") {
2857
2904
  const authServerBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/base`);
2858
2905
  if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, serverAppDir, context);
@@ -4246,6 +4293,35 @@ async function setupAuth(config) {
4246
4293
  projectDir: clientDir
4247
4294
  });
4248
4295
  }
4296
+ if (auth === "better-auth") {
4297
+ const convexBackendDir = path.join(projectDir, "packages/backend");
4298
+ const convexBackendDirExists = await fs.pathExists(convexBackendDir);
4299
+ if (convexBackendDirExists) await addPackageDependency({
4300
+ dependencies: ["better-auth", "@convex-dev/better-auth"],
4301
+ customDependencies: { "better-auth": "1.3.8" },
4302
+ projectDir: convexBackendDir
4303
+ });
4304
+ if (clientDirExists) {
4305
+ const hasNextJs = frontend.includes("next");
4306
+ const hasTanStackStart = frontend.includes("tanstack-start");
4307
+ const hasViteReactOther = frontend.some((f) => ["tanstack-router", "react-router"].includes(f));
4308
+ if (hasNextJs) await addPackageDependency({
4309
+ dependencies: ["better-auth", "@convex-dev/better-auth"],
4310
+ customDependencies: { "better-auth": "1.3.8" },
4311
+ projectDir: clientDir
4312
+ });
4313
+ else if (hasTanStackStart) await addPackageDependency({
4314
+ dependencies: ["better-auth", "@convex-dev/better-auth"],
4315
+ customDependencies: { "better-auth": "1.3.8" },
4316
+ projectDir: clientDir
4317
+ });
4318
+ else if (hasViteReactOther) await addPackageDependency({
4319
+ dependencies: ["better-auth", "@convex-dev/better-auth"],
4320
+ customDependencies: { "better-auth": "1.3.8" },
4321
+ projectDir: clientDir
4322
+ });
4323
+ }
4324
+ }
4249
4325
  const hasNativeWind = frontend.includes("native-nativewind");
4250
4326
  const hasUnistyles = frontend.includes("native-unistyles");
4251
4327
  if (auth === "clerk" && nativeDirExists && (hasNativeWind || hasUnistyles)) await addPackageDependency({
@@ -4307,7 +4383,7 @@ async function addEnvVariablesToFile(filePath, variables) {
4307
4383
  let modified = false;
4308
4384
  let contentToAdd = "";
4309
4385
  const exampleVariables = [];
4310
- for (const { key, value, condition } of variables) if (condition) {
4386
+ for (const { key, value, condition, comment } of variables) if (condition) {
4311
4387
  const regex = new RegExp(`^${key}=.*$`, "m");
4312
4388
  const valueToWrite = value ?? "";
4313
4389
  exampleVariables.push(`${key}=`);
@@ -4318,6 +4394,7 @@ async function addEnvVariablesToFile(filePath, variables) {
4318
4394
  modified = true;
4319
4395
  }
4320
4396
  } else {
4397
+ if (comment) contentToAdd += `# ${comment}\n`;
4321
4398
  contentToAdd += `${key}=${valueToWrite}\n`;
4322
4399
  modified = true;
4323
4400
  }
@@ -4403,6 +4480,18 @@ async function setupEnvironmentVariables(config) {
4403
4480
  });
4404
4481
  }
4405
4482
  }
4483
+ if (backend === "convex" && auth === "better-auth") {
4484
+ if (hasNextJs) clientVars.push({
4485
+ key: "NEXT_PUBLIC_CONVEX_SITE_URL",
4486
+ value: "https://<YOUR_CONVEX_URL>",
4487
+ condition: true
4488
+ });
4489
+ else if (hasReactRouter || hasTanStackRouter || hasTanStackStart) clientVars.push({
4490
+ key: "VITE_CONVEX_SITE_URL",
4491
+ value: "https://<YOUR_CONVEX_URL>",
4492
+ condition: true
4493
+ });
4494
+ }
4406
4495
  await addEnvVariablesToFile(path.join(clientDir, ".env"), clientVars);
4407
4496
  }
4408
4497
  }
@@ -4428,7 +4517,34 @@ async function setupEnvironmentVariables(config) {
4428
4517
  await addEnvVariablesToFile(path.join(nativeDir, ".env"), nativeVars);
4429
4518
  }
4430
4519
  }
4431
- if (backend === "convex") return;
4520
+ if (backend === "convex") {
4521
+ if (auth === "better-auth") {
4522
+ const convexBackendDir = path.join(projectDir, "packages/backend");
4523
+ if (await fs.pathExists(convexBackendDir)) {
4524
+ const envLocalPath = path.join(convexBackendDir, ".env.local");
4525
+ if (!await fs.pathExists(envLocalPath) || !(await fs.readFile(envLocalPath, "utf8")).includes("npx convex env set")) {
4526
+ const convexCommands = `# Set Convex environment variables
4527
+ npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
4528
+ npx convex env set SITE_URL http://localhost:3001
4529
+
4530
+ `;
4531
+ await fs.appendFile(envLocalPath, convexCommands);
4532
+ }
4533
+ const convexBackendVars = [{
4534
+ key: hasNextJs ? "NEXT_PUBLIC_CONVEX_SITE_URL" : "VITE_CONVEX_SITE_URL",
4535
+ value: "",
4536
+ condition: true,
4537
+ comment: "Same as CONVEX_URL but ends in .site"
4538
+ }, {
4539
+ key: "SITE_URL",
4540
+ value: "http://localhost:3001",
4541
+ condition: true
4542
+ }];
4543
+ await addEnvVariablesToFile(envLocalPath, convexBackendVars);
4544
+ }
4545
+ }
4546
+ return;
4547
+ }
4432
4548
  const serverDir = path.join(projectDir, "apps/server");
4433
4549
  if (!await fs.pathExists(serverDir)) return;
4434
4550
  const envPath = path.join(serverDir, ".env");
@@ -5713,17 +5829,6 @@ async function setupNodeRuntime(serverDir, backend) {
5713
5829
  });
5714
5830
  }
5715
5831
 
5716
- //#endregion
5717
- //#region src/helpers/core/convex-codegen.ts
5718
- async function runConvexCodegen(projectDir, packageManager) {
5719
- const backendDir = path.join(projectDir, "packages/backend");
5720
- const cmd = getPackageExecutionCommand(packageManager, "convex codegen");
5721
- await execa(cmd, {
5722
- cwd: backendDir,
5723
- shell: true
5724
- });
5725
- }
5726
-
5727
5832
  //#endregion
5728
5833
  //#region src/helpers/core/create-readme.ts
5729
5834
  async function createReadme(projectDir, options) {
@@ -6515,7 +6620,6 @@ async function createProject(options, cliInput) {
6515
6620
  await setupServerDeploy(options);
6516
6621
  await createReadme(projectDir, options);
6517
6622
  await writeBtsConfig(options);
6518
- if (isConvex) await runConvexCodegen(projectDir, options.packageManager);
6519
6623
  log.success("Project template successfully scaffolded!");
6520
6624
  if (options.install) await installDependencies({
6521
6625
  projectDir,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.46.3",
3
+ "version": "2.47.0",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -48,7 +48,7 @@
48
48
  },
49
49
  "homepage": "https://better-t-stack.dev/",
50
50
  "scripts": {
51
- "build": "tsdown",
51
+ "build": "tsdown --publint",
52
52
  "dev": "tsdown --watch",
53
53
  "check-types": "tsc --noEmit",
54
54
  "check": "biome check --write .",
@@ -82,6 +82,7 @@
82
82
  "@types/fs-extra": "^11.0.4",
83
83
  "@types/node": "^24.3.1",
84
84
  "@vitest/ui": "^3.2.4",
85
+ "publint": "^0.3.12",
85
86
  "tsdown": "^0.14.2",
86
87
  "typescript": "^5.9.2",
87
88
  "vitest": "^3.2.4"
@@ -22,6 +22,7 @@
22
22
  "!**/.expo",
23
23
  "!**/.wrangler",
24
24
  "!**/.alchemy",
25
+ "!**/.svelte-kit",
25
26
  "!**/wrangler.jsonc",
26
27
  "!**/.source"
27
28
  ]
@@ -66,7 +67,7 @@
66
67
  "quoteStyle": "double"
67
68
  }
68
69
  }
69
- {{#if (or (eq frontend "svelte") (eq frontend "nuxt"))}}
70
+ {{#if (or (includes frontend "svelte") (includes frontend "nuxt"))}}
70
71
  ,
71
72
  "overrides": [
72
73
  {
@@ -17,6 +17,7 @@
17
17
  "!**/.expo",
18
18
  "!**/.wrangler",
19
19
  "!**/.alchemy",
20
+ "!**/.svelte-kit",
20
21
  "!**/wrangler.jsonc",
21
22
  "!**/.source"
22
23
  ]
@@ -0,0 +1,8 @@
1
+ export default {
2
+ providers: [
3
+ {
4
+ domain: process.env.CONVEX_SITE_URL,
5
+ applicationID: "convex",
6
+ },
7
+ ],
8
+ };
@@ -0,0 +1,48 @@
1
+ import { createClient, type GenericCtx } from "@convex-dev/better-auth";
2
+ {{#if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
3
+ import { convex } from "@convex-dev/better-auth/plugins";
4
+ {{else}}
5
+ import { convex, crossDomain } from "@convex-dev/better-auth/plugins";
6
+ {{/if}}
7
+ import { components } from "./_generated/api";
8
+ import { DataModel } from "./_generated/dataModel";
9
+ import { query } from "./_generated/server";
10
+ import { betterAuth } from "better-auth";
11
+
12
+ const siteUrl = process.env.SITE_URL!;
13
+
14
+ export const authComponent = createClient<DataModel>(components.betterAuth);
15
+
16
+ export const createAuth = (
17
+ ctx: GenericCtx<DataModel>,
18
+ { optionsOnly } = { optionsOnly: false },
19
+ ) => {
20
+ return betterAuth({
21
+ logger: {
22
+ disabled: optionsOnly,
23
+ },
24
+ {{#if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
25
+ baseUrl: siteUrl,
26
+ {{else}}
27
+ trustedOrigins: [siteUrl],
28
+ {{/if}}
29
+ database: authComponent.adapter(ctx),
30
+ emailAndPassword: {
31
+ enabled: true,
32
+ requireEmailVerification: false,
33
+ },
34
+ plugins: [
35
+ {{#unless (or (includes frontend "tanstack-start") (includes frontend "next"))}}
36
+ crossDomain({ siteUrl }),
37
+ {{/unless}}
38
+ convex(),
39
+ ],
40
+ });
41
+ };
42
+
43
+ export const getCurrentUser = query({
44
+ args: {},
45
+ handler: async (ctx) => {
46
+ return authComponent.getAuthUser(ctx);
47
+ },
48
+ });
@@ -0,0 +1,7 @@
1
+ import { defineApp } from "convex/server";
2
+ import betterAuth from "@convex-dev/better-auth/convex.config";
3
+
4
+ const app = defineApp();
5
+ app.use(betterAuth);
6
+
7
+ export default app;
@@ -0,0 +1,12 @@
1
+ import { httpRouter } from "convex/server";
2
+ import { authComponent, createAuth } from "./auth";
3
+
4
+ const http = httpRouter();
5
+
6
+ {{#if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
7
+ authComponent.registerRoutes(http, createAuth);
8
+ {{else}}
9
+ authComponent.registerRoutes(http, createAuth, { cors: true });
10
+ {{/if}}
11
+
12
+ export default http;
@@ -0,0 +1,16 @@
1
+ import { query } from "./_generated/server";
2
+
3
+ export const get = query({
4
+ args: {},
5
+ handler: async (ctx) => {
6
+ const identity = await ctx.auth.getUserIdentity();
7
+ if (identity === null) {
8
+ return {
9
+ message: "Not authenticated",
10
+ };
11
+ }
12
+ return {
13
+ message: "This is private",
14
+ };
15
+ },
16
+ });
@@ -0,0 +1,3 @@
1
+ import { nextJsHandler } from "@convex-dev/better-auth/nextjs";
2
+
3
+ export const { GET, POST } = nextJsHandler();
@@ -0,0 +1,40 @@
1
+ "use client"
2
+
3
+ import SignInForm from "@/components/sign-in-form";
4
+ import SignUpForm from "@/components/sign-up-form";
5
+ import UserMenu from "@/components/user-menu";
6
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
7
+ import {
8
+ Authenticated,
9
+ AuthLoading,
10
+ Unauthenticated,
11
+ useQuery,
12
+ } from "convex/react";
13
+ import { useState } from "react";
14
+
15
+ export default function DashboardPage() {
16
+ const [showSignIn, setShowSignIn] = useState(false);
17
+ const privateData = useQuery(api.privateData.get);
18
+
19
+ return (
20
+ <>
21
+ <Authenticated>
22
+ <div>
23
+ <h1>Dashboard</h1>
24
+ <p>privateData: {privateData?.message}</p>
25
+ <UserMenu />
26
+ </div>
27
+ </Authenticated>
28
+ <Unauthenticated>
29
+ {showSignIn ? (
30
+ <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />
31
+ ) : (
32
+ <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />
33
+ )}
34
+ </Unauthenticated>
35
+ <AuthLoading>
36
+ <div>Loading...</div>
37
+ </AuthLoading>
38
+ </>
39
+ );
40
+ }
@@ -0,0 +1,129 @@
1
+ import { authClient } from "@/lib/auth-client";
2
+ import { useForm } from "@tanstack/react-form";
3
+ import { toast } from "sonner";
4
+ import z from "zod";
5
+ import { Button } from "./ui/button";
6
+ import { Input } from "./ui/input";
7
+ import { Label } from "./ui/label";
8
+ import { useRouter } from "next/navigation";
9
+
10
+ export default function SignInForm({
11
+ onSwitchToSignUp,
12
+ }: {
13
+ onSwitchToSignUp: () => void;
14
+ }) {
15
+ const router = useRouter();
16
+
17
+ const form = useForm({
18
+ defaultValues: {
19
+ email: "",
20
+ password: "",
21
+ },
22
+ onSubmit: async ({ value }) => {
23
+ await authClient.signIn.email(
24
+ {
25
+ email: value.email,
26
+ password: value.password,
27
+ },
28
+ {
29
+ onSuccess: () => {
30
+ router.push("/dashboard");
31
+ toast.success("Sign in successful");
32
+ },
33
+ onError: (error) => {
34
+ toast.error(error.error.message || error.error.statusText);
35
+ },
36
+ },
37
+ );
38
+ },
39
+ validators: {
40
+ onSubmit: z.object({
41
+ email: z.email("Invalid email address"),
42
+ password: z.string().min(8, "Password must be at least 8 characters"),
43
+ }),
44
+ },
45
+ });
46
+
47
+ return (
48
+ <div className="mx-auto w-full mt-10 max-w-md p-6">
49
+ <h1 className="mb-6 text-center text-3xl font-bold">Welcome Back</h1>
50
+
51
+ <form
52
+ onSubmit={(e) => {
53
+ e.preventDefault();
54
+ e.stopPropagation();
55
+ form.handleSubmit();
56
+ }}
57
+ className="space-y-4"
58
+ >
59
+ <div>
60
+ <form.Field name="email">
61
+ {(field) => (
62
+ <div className="space-y-2">
63
+ <Label htmlFor={field.name}>Email</Label>
64
+ <Input
65
+ id={field.name}
66
+ name={field.name}
67
+ type="email"
68
+ value={field.state.value}
69
+ onBlur={field.handleBlur}
70
+ onChange={(e) => field.handleChange(e.target.value)}
71
+ />
72
+ {field.state.meta.errors.map((error) => (
73
+ <p key={error?.message} className="text-red-500">
74
+ {error?.message}
75
+ </p>
76
+ ))}
77
+ </div>
78
+ )}
79
+ </form.Field>
80
+ </div>
81
+
82
+ <div>
83
+ <form.Field name="password">
84
+ {(field) => (
85
+ <div className="space-y-2">
86
+ <Label htmlFor={field.name}>Password</Label>
87
+ <Input
88
+ id={field.name}
89
+ name={field.name}
90
+ type="password"
91
+ value={field.state.value}
92
+ onBlur={field.handleBlur}
93
+ onChange={(e) => field.handleChange(e.target.value)}
94
+ />
95
+ {field.state.meta.errors.map((error) => (
96
+ <p key={error?.message} className="text-red-500">
97
+ {error?.message}
98
+ </p>
99
+ ))}
100
+ </div>
101
+ )}
102
+ </form.Field>
103
+ </div>
104
+
105
+ <form.Subscribe>
106
+ {(state) => (
107
+ <Button
108
+ type="submit"
109
+ className="w-full"
110
+ disabled={!state.canSubmit || state.isSubmitting}
111
+ >
112
+ {state.isSubmitting ? "Submitting..." : "Sign In"}
113
+ </Button>
114
+ )}
115
+ </form.Subscribe>
116
+ </form>
117
+
118
+ <div className="mt-4 text-center">
119
+ <Button
120
+ variant="link"
121
+ onClick={onSwitchToSignUp}
122
+ className="text-indigo-600 hover:text-indigo-800"
123
+ >
124
+ Need an account? Sign Up
125
+ </Button>
126
+ </div>
127
+ </div>
128
+ );
129
+ }