create-better-t-stack 2.46.2 → 2.46.3-canary.62efbc44

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 (37) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/{src-NOw0j6Z9.js → src-DHpq-szu.js} +132 -35
  4. package/package.json +3 -2
  5. package/templates/auth/better-auth/convex/backend/convex/auth.config.ts.hbs +8 -0
  6. package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +48 -0
  7. package/templates/auth/better-auth/convex/backend/convex/convex.config.ts.hbs +7 -0
  8. package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +12 -0
  9. package/templates/auth/better-auth/convex/backend/convex/privateData.ts.hbs +16 -0
  10. package/templates/auth/better-auth/convex/web/react/next/src/app/api/auth/[...all]/route.ts.hbs +3 -0
  11. package/templates/auth/better-auth/convex/web/react/next/src/app/dashboard/page.tsx.hbs +40 -0
  12. package/templates/auth/better-auth/convex/web/react/next/src/components/sign-in-form.tsx.hbs +129 -0
  13. package/templates/auth/better-auth/convex/web/react/next/src/components/sign-up-form.tsx.hbs +154 -0
  14. package/templates/auth/better-auth/convex/web/react/next/src/components/user-menu.tsx.hbs +48 -0
  15. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-client.ts.hbs +6 -0
  16. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-server.ts.hbs +6 -0
  17. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/sign-in-form.tsx.hbs +133 -0
  18. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/sign-up-form.tsx.hbs +158 -0
  19. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/user-menu.tsx.hbs +50 -0
  20. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/lib/auth-client.ts.hbs +10 -0
  21. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs +43 -0
  22. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/sign-in-form.tsx.hbs +133 -0
  23. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/sign-up-form.tsx.hbs +158 -0
  24. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/user-menu.tsx.hbs +50 -0
  25. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-client.ts.hbs +6 -0
  26. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-server.ts.hbs +5 -0
  27. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/api/auth/$.ts.hbs +11 -0
  28. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs +43 -0
  29. package/templates/examples/ai/web/react/next/src/app/ai/page.tsx.hbs +21 -2
  30. package/templates/frontend/react/next/next.config.ts.hbs +4 -1
  31. package/templates/frontend/react/next/src/components/providers.tsx.hbs +8 -0
  32. package/templates/frontend/react/tanstack-router/src/main.tsx.hbs +8 -1
  33. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +47 -0
  34. package/templates/frontend/react/tanstack-start/src/routes/index.tsx.hbs +2 -2
  35. package/templates/frontend/react/web-base/src/components/header.tsx.hbs +2 -2
  36. package/templates/auth/better-auth/web/react/next/src/components/theme-provider.tsx.hbs +0 -11
  37. /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-NOw0j6Z9.js";
2
+ import { createBtsCli } from "./src-DHpq-szu.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-NOw0j6Z9.js";
2
+ import { builder, createBtsCli, docs, init, router, sponsors } from "./src-DHpq-szu.js";
3
3
 
4
4
  export { builder, createBtsCli, docs, init, router, sponsors };
@@ -112,7 +112,8 @@ const dependencyVersionMap = {
112
112
  "@ai-sdk/vue": "^2.0.39",
113
113
  "@ai-sdk/svelte": "^3.0.39",
114
114
  "@ai-sdk/react": "^2.0.39",
115
- streamdown: "^1.2.0",
115
+ streamdown: "^1.3.0",
116
+ shiki: "^3.12.2",
116
117
  "@orpc/server": "^1.8.6",
117
118
  "@orpc/client": "^1.8.6",
118
119
  "@orpc/openapi": "^1.8.6",
@@ -121,11 +122,12 @@ const dependencyVersionMap = {
121
122
  "@trpc/tanstack-react-query": "^11.5.0",
122
123
  "@trpc/server": "^11.5.0",
123
124
  "@trpc/client": "^11.5.0",
124
- convex: "^1.25.4",
125
+ convex: "^1.27.0",
125
126
  "@convex-dev/react-query": "^0.0.0-alpha.8",
126
127
  "convex-svelte": "^0.0.11",
127
128
  "convex-nuxt": "0.1.5",
128
129
  "convex-vue": "^0.1.5",
130
+ "@convex-dev/better-auth": "^0.8.4",
129
131
  "@tanstack/svelte-query": "^5.85.3",
130
132
  "@tanstack/svelte-query-devtools": "^5.85.3",
131
133
  "@tanstack/vue-query-devtools": "^5.83.0",
@@ -141,7 +143,7 @@ const dependencyVersionMap = {
141
143
  "nitro-cloudflare-dev": "^0.2.2",
142
144
  "@sveltejs/adapter-cloudflare": "^7.2.1",
143
145
  "@cloudflare/workers-types": "^4.20250822.0",
144
- alchemy: "^0.65.1",
146
+ alchemy: "^0.67.0",
145
147
  nitropack: "^2.12.4",
146
148
  dotenv: "^17.2.1",
147
149
  "@polar-sh/better-auth": "^1.1.3",
@@ -422,11 +424,10 @@ function validateAddonsAgainstFrontends(addons = [], frontends = [], auth) {
422
424
  if (!isCompatible) exitWithError(`Incompatible addon/frontend combination: ${reason}`);
423
425
  }
424
426
  }
425
- function validatePaymentsCompatibility(payments, auth, backend, frontends = []) {
427
+ function validatePaymentsCompatibility(payments, auth, _backend, frontends = []) {
426
428
  if (!payments || payments === "none") return;
427
429
  if (payments === "polar") {
428
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.");
429
- if (backend === "convex") exitWithError("Polar payments is not compatible with Convex backend. Please use a different backend or choose a different payments provider.");
430
431
  const { web } = splitFrontends(frontends);
431
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.");
432
433
  }
@@ -613,20 +614,36 @@ async function getAuthChoice(auth, hasDatabase, backend, frontend) {
613
614
  const unsupportedFrontends = frontend?.filter((f) => [
614
615
  "nuxt",
615
616
  "svelte",
616
- "solid"
617
+ "solid",
618
+ "native-nativewind",
619
+ "native-unistyles"
617
620
  ].includes(f));
618
621
  if (unsupportedFrontends && unsupportedFrontends.length > 0) return "none";
622
+ const hasReactFrontends = frontend?.some((f) => [
623
+ "react-router",
624
+ "tanstack-router",
625
+ "tanstack-start",
626
+ "next"
627
+ ].includes(f));
628
+ const options = [{
629
+ value: "clerk",
630
+ label: "Clerk",
631
+ hint: "More than auth, Complete User Management"
632
+ }];
633
+ if (hasReactFrontends) options.unshift({
634
+ value: "better-auth",
635
+ label: "Better-Auth",
636
+ hint: "comprehensive auth framework for TypeScript"
637
+ });
638
+ options.push({
639
+ value: "none",
640
+ label: "None",
641
+ hint: "No auth"
642
+ });
619
643
  const response$1 = await select({
620
644
  message: "Select authentication provider",
621
- options: [{
622
- value: "clerk",
623
- label: "Clerk",
624
- hint: "More than auth, Complete User Management"
625
- }, {
626
- value: "none",
627
- label: "None"
628
- }],
629
- initialValue: "clerk"
645
+ options,
646
+ initialValue: "none"
630
647
  });
631
648
  if (isCancel(response$1)) return exitCancelled("Operation cancelled");
632
649
  return response$1;
@@ -1369,7 +1386,7 @@ const getLatestCLIVersion = () => {
1369
1386
  */
1370
1387
  function isTelemetryEnabled() {
1371
1388
  const BTS_TELEMETRY_DISABLED = process.env.BTS_TELEMETRY_DISABLED;
1372
- const BTS_TELEMETRY = "1";
1389
+ const BTS_TELEMETRY = "0";
1373
1390
  if (BTS_TELEMETRY_DISABLED !== void 0) return BTS_TELEMETRY_DISABLED !== "1";
1374
1391
  if (BTS_TELEMETRY !== void 0) return BTS_TELEMETRY === "1";
1375
1392
  return true;
@@ -1377,8 +1394,8 @@ function isTelemetryEnabled() {
1377
1394
 
1378
1395
  //#endregion
1379
1396
  //#region src/utils/analytics.ts
1380
- const POSTHOG_API_KEY = "phc_8ZUxEwwfKMajJLvxz1daGd931dYbQrwKNficBmsdIrs";
1381
- const POSTHOG_HOST = "https://us.i.posthog.com";
1397
+ const POSTHOG_API_KEY = "random";
1398
+ const POSTHOG_HOST = "random";
1382
1399
  function generateSessionId() {
1383
1400
  const rand = Math.random().toString(36).slice(2);
1384
1401
  const now = Date.now().toString(36);
@@ -1732,7 +1749,16 @@ function validateConvexConstraints(config, providedFlags) {
1732
1749
  if (has("api") && config.api !== "none") exitWithError("Convex backend requires '--api none'. Please remove the --api flag or set it to 'none'.");
1733
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'.");
1734
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'.");
1735
- 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 hasUnsupportedFrontends = config.frontend?.some((f) => [
1754
+ "nuxt",
1755
+ "svelte",
1756
+ "solid",
1757
+ "native-nativewind",
1758
+ "native-unistyles"
1759
+ ].includes(f));
1760
+ if (hasUnsupportedFrontends) exitWithError("Better-Auth with Convex backend is not supported for non-React frontends (nuxt, svelte, solid) or native frontends (native-nativewind, native-unistyles). Please use '--auth clerk' or '--auth none'.");
1761
+ }
1736
1762
  }
1737
1763
  function validateBackendNoneConstraints(config, providedFlags) {
1738
1764
  const { backend } = config;
@@ -1979,18 +2005,18 @@ async function updateBtsConfig(projectDir, updates) {
1979
2005
  //#endregion
1980
2006
  //#region src/utils/add-package-deps.ts
1981
2007
  const addPackageDependency = async (opts) => {
1982
- const { dependencies = [], devDependencies = [], projectDir } = opts;
2008
+ const { dependencies = [], devDependencies = [], customDependencies = {}, customDevDependencies = {}, projectDir } = opts;
1983
2009
  const pkgJsonPath = path.join(projectDir, "package.json");
1984
2010
  const pkgJson = await fs.readJson(pkgJsonPath);
1985
2011
  if (!pkgJson.dependencies) pkgJson.dependencies = {};
1986
2012
  if (!pkgJson.devDependencies) pkgJson.devDependencies = {};
1987
2013
  for (const pkgName of dependencies) {
1988
- const version = dependencyVersionMap[pkgName];
2014
+ const version = customDependencies[pkgName] || dependencyVersionMap[pkgName];
1989
2015
  if (version) pkgJson.dependencies[pkgName] = version;
1990
2016
  else console.warn(`Warning: Dependency ${pkgName} not found in version map.`);
1991
2017
  }
1992
2018
  for (const pkgName of devDependencies) {
1993
- const version = dependencyVersionMap[pkgName];
2019
+ const version = customDevDependencies[pkgName] || dependencyVersionMap[pkgName];
1994
2020
  if (version) pkgJson.devDependencies[pkgName] = version;
1995
2021
  else console.warn(`Warning: Dev dependency ${pkgName} not found in version map.`);
1996
2022
  }
@@ -2852,6 +2878,29 @@ async function setupAuthTemplate(projectDir, context) {
2852
2878
  }
2853
2879
  return;
2854
2880
  }
2881
+ if (context.backend === "convex" && authProvider === "better-auth") {
2882
+ const convexBackendDestDir = path.join(projectDir, "packages/backend");
2883
+ const convexBetterAuthBackendSrc = path.join(PKG_ROOT, "templates/auth/better-auth/convex/backend");
2884
+ if (await fs.pathExists(convexBetterAuthBackendSrc)) {
2885
+ await fs.ensureDir(convexBackendDestDir);
2886
+ await processAndCopyFiles("**/*", convexBetterAuthBackendSrc, convexBackendDestDir, context);
2887
+ }
2888
+ if (webAppDirExists && hasReactWeb) {
2889
+ const convexBetterAuthWebBaseSrc = path.join(PKG_ROOT, "templates/auth/better-auth/convex/web/react/base");
2890
+ if (await fs.pathExists(convexBetterAuthWebBaseSrc)) await processAndCopyFiles("**/*", convexBetterAuthWebBaseSrc, webAppDir, context);
2891
+ const reactFramework = context.frontend.find((f) => [
2892
+ "tanstack-router",
2893
+ "react-router",
2894
+ "tanstack-start",
2895
+ "next"
2896
+ ].includes(f));
2897
+ if (reactFramework) {
2898
+ const convexBetterAuthWebSrc = path.join(PKG_ROOT, `templates/auth/better-auth/convex/web/react/${reactFramework}`);
2899
+ if (await fs.pathExists(convexBetterAuthWebSrc)) await processAndCopyFiles("**/*", convexBetterAuthWebSrc, webAppDir, context);
2900
+ }
2901
+ }
2902
+ return;
2903
+ }
2855
2904
  if (serverAppDirExists && context.backend !== "convex") {
2856
2905
  const authServerBaseSrc = path.join(PKG_ROOT, `templates/auth/${authProvider}/server/base`);
2857
2906
  if (await fs.pathExists(authServerBaseSrc)) await processAndCopyFiles("**/*", authServerBaseSrc, serverAppDir, context);
@@ -3974,12 +4023,14 @@ async function setupExamples(config) {
3974
4023
  const hasNuxt = frontend.includes("nuxt");
3975
4024
  const hasSvelte = frontend.includes("svelte");
3976
4025
  const hasReactWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("next") || frontend.includes("tanstack-start");
4026
+ const hasNext = frontend.includes("next");
3977
4027
  const hasReactNative = frontend.includes("native-nativewind") || frontend.includes("native-unistyles");
3978
4028
  if (webClientDirExists) {
3979
4029
  const dependencies = ["ai"];
3980
4030
  if (hasNuxt) dependencies.push("@ai-sdk/vue");
3981
4031
  else if (hasSvelte) dependencies.push("@ai-sdk/svelte");
3982
4032
  else if (hasReactWeb) dependencies.push("@ai-sdk/react", "streamdown");
4033
+ if (hasNext) dependencies.push("shiki");
3983
4034
  await addPackageDependency({
3984
4035
  dependencies,
3985
4036
  projectDir: webClientDir
@@ -4243,6 +4294,35 @@ async function setupAuth(config) {
4243
4294
  projectDir: clientDir
4244
4295
  });
4245
4296
  }
4297
+ if (auth === "better-auth") {
4298
+ const convexBackendDir = path.join(projectDir, "packages/backend");
4299
+ const convexBackendDirExists = await fs.pathExists(convexBackendDir);
4300
+ if (convexBackendDirExists) await addPackageDependency({
4301
+ dependencies: ["better-auth", "@convex-dev/better-auth"],
4302
+ customDependencies: { "better-auth": "1.3.8" },
4303
+ projectDir: convexBackendDir
4304
+ });
4305
+ if (clientDirExists) {
4306
+ const hasNextJs = frontend.includes("next");
4307
+ const hasTanStackStart = frontend.includes("tanstack-start");
4308
+ const hasViteReactOther = frontend.some((f) => ["tanstack-router", "react-router"].includes(f));
4309
+ if (hasNextJs) await addPackageDependency({
4310
+ dependencies: ["better-auth", "@convex-dev/better-auth"],
4311
+ customDependencies: { "better-auth": "1.3.8" },
4312
+ projectDir: clientDir
4313
+ });
4314
+ else if (hasTanStackStart) await addPackageDependency({
4315
+ dependencies: ["better-auth", "@convex-dev/better-auth"],
4316
+ customDependencies: { "better-auth": "1.3.8" },
4317
+ projectDir: clientDir
4318
+ });
4319
+ else if (hasViteReactOther) await addPackageDependency({
4320
+ dependencies: ["better-auth", "@convex-dev/better-auth"],
4321
+ customDependencies: { "better-auth": "1.3.8" },
4322
+ projectDir: clientDir
4323
+ });
4324
+ }
4325
+ }
4246
4326
  const hasNativeWind = frontend.includes("native-nativewind");
4247
4327
  const hasUnistyles = frontend.includes("native-unistyles");
4248
4328
  if (auth === "clerk" && nativeDirExists && (hasNativeWind || hasUnistyles)) await addPackageDependency({
@@ -4400,6 +4480,18 @@ async function setupEnvironmentVariables(config) {
4400
4480
  });
4401
4481
  }
4402
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
+ }
4403
4495
  await addEnvVariablesToFile(path.join(clientDir, ".env"), clientVars);
4404
4496
  }
4405
4497
  }
@@ -4425,7 +4517,24 @@ async function setupEnvironmentVariables(config) {
4425
4517
  await addEnvVariablesToFile(path.join(nativeDir, ".env"), nativeVars);
4426
4518
  }
4427
4519
  }
4428
- 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 convexBackendVars = [{
4525
+ key: hasNextJs ? "NEXT_PUBLIC_CONVEX_SITE_URL" : "VITE_CONVEX_SITE_URL",
4526
+ value: "",
4527
+ condition: true
4528
+ }, {
4529
+ key: "SITE_URL",
4530
+ value: "http://localhost:3001",
4531
+ condition: true
4532
+ }];
4533
+ await addEnvVariablesToFile(path.join(convexBackendDir, ".env.local"), convexBackendVars);
4534
+ }
4535
+ }
4536
+ return;
4537
+ }
4429
4538
  const serverDir = path.join(projectDir, "apps/server");
4430
4539
  if (!await fs.pathExists(serverDir)) return;
4431
4540
  const envPath = path.join(serverDir, ".env");
@@ -5710,17 +5819,6 @@ async function setupNodeRuntime(serverDir, backend) {
5710
5819
  });
5711
5820
  }
5712
5821
 
5713
- //#endregion
5714
- //#region src/helpers/core/convex-codegen.ts
5715
- async function runConvexCodegen(projectDir, packageManager) {
5716
- const backendDir = path.join(projectDir, "packages/backend");
5717
- const cmd = getPackageExecutionCommand(packageManager, "convex codegen");
5718
- await execa(cmd, {
5719
- cwd: backendDir,
5720
- shell: true
5721
- });
5722
- }
5723
-
5724
5822
  //#endregion
5725
5823
  //#region src/helpers/core/create-readme.ts
5726
5824
  async function createReadme(projectDir, options) {
@@ -6512,7 +6610,6 @@ async function createProject(options, cliInput) {
6512
6610
  await setupServerDeploy(options);
6513
6611
  await createReadme(projectDir, options);
6514
6612
  await writeBtsConfig(options);
6515
- if (isConvex) await runConvexCodegen(projectDir, options.packageManager);
6516
6613
  log.success("Project template successfully scaffolded!");
6517
6614
  if (options.install) await installDependencies({
6518
6615
  projectDir,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.46.2",
3
+ "version": "2.46.3-canary.62efbc44",
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"
@@ -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 (eq frontend "tanstack-start") (eq 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 (eq frontend "tanstack-start") (eq 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 (eq frontend "tanstack-start") (eq 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 (eq frontend "tanstack-start") (eq 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
+ }