@better-t-stack/template-generator 3.25.5 → 3.26.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.
package/dist/index.mjs CHANGED
@@ -646,7 +646,7 @@ function updateEnvPackageJson(vfs, config) {
646
646
  const pkgJson = vfs.readJson("packages/env/package.json");
647
647
  if (!pkgJson) return;
648
648
  pkgJson.name = `@${config.projectName}/env`;
649
- const hasWebFrontend = config.frontend.some((f) => desktopWebFrontends.includes(f));
649
+ const hasWebFrontend$1 = config.frontend.some((f) => desktopWebFrontends.includes(f));
650
650
  const hasNative = config.frontend.some((f) => [
651
651
  "native-bare",
652
652
  "native-uniwind",
@@ -655,7 +655,7 @@ function updateEnvPackageJson(vfs, config) {
655
655
  const needsServerEnv = config.backend !== "none" && config.backend !== "convex";
656
656
  const exports = {};
657
657
  if (needsServerEnv) exports["./server"] = "./src/server.ts";
658
- if (hasWebFrontend) exports["./web"] = "./src/web.ts";
658
+ if (hasWebFrontend$1) exports["./web"] = "./src/web.ts";
659
659
  if (hasNative) exports["./native"] = "./src/native.ts";
660
660
  pkgJson.exports = exports;
661
661
  vfs.writeJson("packages/env/package.json", pkgJson);
@@ -707,10 +707,14 @@ const dependencyVersionMap = {
707
707
  typescript: "^5",
708
708
  "better-auth": "1.5.2",
709
709
  "@better-auth/expo": "1.5.2",
710
- "@clerk/nextjs": "^6.31.5",
711
- "@clerk/clerk-react": "^5.45.0",
712
- "@clerk/tanstack-react-start": "^0.26.3",
713
- "@clerk/clerk-expo": "^2.14.25",
710
+ "@clerk/backend": "^3.2.1",
711
+ "@clerk/express": "^2.0.5",
712
+ "@clerk/fastify": "^3.1.3",
713
+ "@clerk/nextjs": "^7.0.5",
714
+ "@clerk/react": "^6.1.1",
715
+ "@clerk/react-router": "^3.0.5",
716
+ "@clerk/tanstack-react-start": "^1.0.5",
717
+ "@clerk/expo": "^3.1.3",
714
718
  "drizzle-orm": "^0.45.1",
715
719
  "drizzle-kit": "^0.31.8",
716
720
  "@planetscale/database": "^1.19.0",
@@ -775,7 +779,7 @@ const dependencyVersionMap = {
775
779
  "@trpc/tanstack-react-query": "^11.7.2",
776
780
  "@trpc/server": "^11.7.2",
777
781
  "@trpc/client": "^11.7.2",
778
- next: "^16.1.1",
782
+ next: "^16.2.0",
779
783
  convex: "^1.32.0",
780
784
  "@convex-dev/react-query": "^0.1.0",
781
785
  "@convex-dev/agent": "^0.3.2",
@@ -1351,8 +1355,10 @@ function processConvexAuthDeps(vfs, config) {
1351
1355
  "native-unistyles"
1352
1356
  ].includes(f));
1353
1357
  const hasNextJs = frontend.includes("next");
1358
+ const hasReactRouter = frontend.includes("react-router");
1359
+ const hasTanStackRouter = frontend.includes("tanstack-router");
1354
1360
  const hasTanStackStart = frontend.includes("tanstack-start");
1355
- const hasViteReact = frontend.some((f) => ["tanstack-router", "react-router"].includes(f));
1361
+ const hasViteReact = hasReactRouter || hasTanStackRouter;
1356
1362
  const hasSolid = frontend.includes("solid");
1357
1363
  const hasSvelte = frontend.includes("svelte");
1358
1364
  const hasReactWebAuthForms = hasNextJs || hasTanStackStart || hasViteReact;
@@ -1363,21 +1369,26 @@ function processConvexAuthDeps(vfs, config) {
1363
1369
  packagePath: webPath,
1364
1370
  dependencies: ["@clerk/nextjs"]
1365
1371
  });
1366
- else if (hasTanStackStart) addPackageDependency({
1372
+ else if (hasReactRouter) addPackageDependency({
1367
1373
  vfs,
1368
1374
  packagePath: webPath,
1369
- dependencies: ["@clerk/tanstack-react-start"]
1375
+ dependencies: ["@clerk/react-router"]
1370
1376
  });
1371
- else if (hasViteReact) addPackageDependency({
1377
+ else if (hasTanStackRouter) addPackageDependency({
1378
+ vfs,
1379
+ packagePath: webPath,
1380
+ dependencies: ["@clerk/react"]
1381
+ });
1382
+ else if (hasTanStackStart) addPackageDependency({
1372
1383
  vfs,
1373
1384
  packagePath: webPath,
1374
- dependencies: ["@clerk/clerk-react"]
1385
+ dependencies: ["@clerk/tanstack-react-start"]
1375
1386
  });
1376
1387
  }
1377
1388
  if (nativeExists && hasNative) addPackageDependency({
1378
1389
  vfs,
1379
1390
  packagePath: nativePath,
1380
- dependencies: ["@clerk/clerk-expo"]
1391
+ dependencies: ["@clerk/expo"]
1381
1392
  });
1382
1393
  } else if (auth === "better-auth") {
1383
1394
  if (backendExists) {
@@ -1434,19 +1445,23 @@ function processConvexAuthDeps(vfs, config) {
1434
1445
  }
1435
1446
  }
1436
1447
  function processStandardAuthDeps(vfs, config) {
1437
- const { auth, frontend } = config;
1448
+ const { auth, backend, frontend } = config;
1438
1449
  const authPath = "packages/auth/package.json";
1450
+ const apiPath = "packages/api/package.json";
1439
1451
  const webPath = "apps/web/package.json";
1440
1452
  const nativePath = "apps/native/package.json";
1453
+ const serverPath = "apps/server/package.json";
1441
1454
  const authExists = vfs.exists(authPath);
1455
+ const apiExists = vfs.exists(apiPath);
1442
1456
  const webExists = vfs.exists(webPath);
1443
1457
  const nativeExists = vfs.exists(nativePath);
1458
+ const serverExists = vfs.exists(serverPath);
1444
1459
  const hasNative = frontend.some((f) => [
1445
1460
  "native-bare",
1446
1461
  "native-uniwind",
1447
1462
  "native-unistyles"
1448
1463
  ].includes(f));
1449
- const hasWebFrontend = frontend.some((f) => [
1464
+ const hasWebFrontend$1 = frontend.some((f) => [
1450
1465
  "react-router",
1451
1466
  "tanstack-router",
1452
1467
  "tanstack-start",
@@ -1464,7 +1479,68 @@ function processStandardAuthDeps(vfs, config) {
1464
1479
  ].includes(f));
1465
1480
  const hasSolid = frontend.includes("solid");
1466
1481
  const hasSvelte = frontend.includes("svelte");
1467
- if (auth === "better-auth") {
1482
+ const hasNextJs = frontend.includes("next");
1483
+ const hasReactRouter = frontend.includes("react-router");
1484
+ const hasTanStackRouter = frontend.includes("tanstack-router");
1485
+ const hasTanStackStart = frontend.includes("tanstack-start");
1486
+ if (auth === "clerk") {
1487
+ if (webExists) {
1488
+ if (hasNextJs) addPackageDependency({
1489
+ vfs,
1490
+ packagePath: webPath,
1491
+ dependencies: ["@clerk/nextjs"]
1492
+ });
1493
+ else if (hasReactRouter) addPackageDependency({
1494
+ vfs,
1495
+ packagePath: webPath,
1496
+ dependencies: ["@clerk/react-router"]
1497
+ });
1498
+ else if (hasTanStackRouter) addPackageDependency({
1499
+ vfs,
1500
+ packagePath: webPath,
1501
+ dependencies: ["@clerk/react"]
1502
+ });
1503
+ else if (hasTanStackStart) addPackageDependency({
1504
+ vfs,
1505
+ packagePath: webPath,
1506
+ dependencies: ["@clerk/tanstack-react-start"]
1507
+ });
1508
+ }
1509
+ if (hasNative && nativeExists) addPackageDependency({
1510
+ vfs,
1511
+ packagePath: nativePath,
1512
+ dependencies: ["@clerk/expo"]
1513
+ });
1514
+ if (apiExists) {
1515
+ if (backend === "self" || backend === "hono" || backend === "elysia") addPackageDependency({
1516
+ vfs,
1517
+ packagePath: apiPath,
1518
+ dependencies: ["@clerk/backend"]
1519
+ });
1520
+ else if (backend === "express") addPackageDependency({
1521
+ vfs,
1522
+ packagePath: apiPath,
1523
+ dependencies: ["@clerk/express"]
1524
+ });
1525
+ else if (backend === "fastify") addPackageDependency({
1526
+ vfs,
1527
+ packagePath: apiPath,
1528
+ dependencies: ["@clerk/fastify"]
1529
+ });
1530
+ }
1531
+ if (serverExists) {
1532
+ if (backend === "express") addPackageDependency({
1533
+ vfs,
1534
+ packagePath: serverPath,
1535
+ dependencies: ["@clerk/express"]
1536
+ });
1537
+ else if (backend === "fastify") addPackageDependency({
1538
+ vfs,
1539
+ packagePath: serverPath,
1540
+ dependencies: ["@clerk/fastify"]
1541
+ });
1542
+ }
1543
+ } else if (auth === "better-auth") {
1468
1544
  if (authExists) {
1469
1545
  addPackageDependency({
1470
1546
  vfs,
@@ -1477,7 +1553,7 @@ function processStandardAuthDeps(vfs, config) {
1477
1553
  dependencies: ["@better-auth/expo"]
1478
1554
  });
1479
1555
  }
1480
- if (hasWebFrontend && webExists) {
1556
+ if (hasWebFrontend$1 && webExists) {
1481
1557
  addPackageDependency({
1482
1558
  vfs,
1483
1559
  packagePath: webPath,
@@ -1907,12 +1983,8 @@ function buildClientVars(frontend, backend, auth) {
1907
1983
  value: backend === "convex" ? "https://<YOUR_CONVEX_URL>" : baseVar.value,
1908
1984
  condition: backend === "convex" ? true : baseVar.write
1909
1985
  }];
1910
- if (backend === "convex" && auth === "clerk") {
1986
+ if (auth === "clerk") {
1911
1987
  if (hasNextJs) vars.push({
1912
- key: "NEXT_PUBLIC_CLERK_FRONTEND_API_URL",
1913
- value: "",
1914
- condition: true
1915
- }, {
1916
1988
  key: "NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY",
1917
1989
  value: "",
1918
1990
  condition: true
@@ -1927,7 +1999,7 @@ function buildClientVars(frontend, backend, auth) {
1927
1999
  value: "",
1928
2000
  condition: true
1929
2001
  });
1930
- if (hasTanStackStart) vars.push({
2002
+ if (hasReactRouter || hasTanStackStart) vars.push({
1931
2003
  key: "CLERK_SECRET_KEY",
1932
2004
  value: "",
1933
2005
  condition: true
@@ -1962,7 +2034,7 @@ function buildNativeVars(frontend, backend, auth) {
1962
2034
  value: serverUrl,
1963
2035
  condition: true
1964
2036
  }];
1965
- if (backend === "convex" && auth === "clerk") vars.push({
2037
+ if (auth === "clerk") vars.push({
1966
2038
  key: "EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY",
1967
2039
  value: "",
1968
2040
  condition: true
@@ -2027,7 +2099,7 @@ function buildConvexCommentBlocks(frontend, auth, examples) {
2027
2099
  ${hasWeb || hasNative ? `# npx convex env set SITE_URL ${defaultSiteUrl}\n` : ""}`;
2028
2100
  return commentBlocks;
2029
2101
  }
2030
- function buildServerVars(backend, frontend, auth, database, dbSetup, runtime, webDeploy, serverDeploy, payments, examples) {
2102
+ function buildServerVars(backend, frontend, auth, api, database, dbSetup, runtime, webDeploy, serverDeploy, payments, examples) {
2031
2103
  const hasReactRouter = frontend.includes("react-router");
2032
2104
  const hasSvelte = frontend.includes("svelte");
2033
2105
  const hasAstro = frontend.includes("astro");
@@ -2052,6 +2124,12 @@ function buildServerVars(backend, frontend, auth, database, dbSetup, runtime, we
2052
2124
  break;
2053
2125
  }
2054
2126
  const hasBetterAuth = auth === "better-auth";
2127
+ const hasClerk = auth === "clerk";
2128
+ const needsClerkPublishableKey = hasClerk && (["express", "fastify"].includes(backend) || api !== "none" && [
2129
+ "self",
2130
+ "hono",
2131
+ "elysia"
2132
+ ].includes(backend));
2055
2133
  return [
2056
2134
  {
2057
2135
  key: "BETTER_AUTH_SECRET",
@@ -2063,6 +2141,16 @@ function buildServerVars(backend, frontend, auth, database, dbSetup, runtime, we
2063
2141
  value: backend === "self" ? hasAstro ? "http://localhost:4321" : "http://localhost:3001" : "http://localhost:3000",
2064
2142
  condition: hasBetterAuth
2065
2143
  },
2144
+ {
2145
+ key: "CLERK_SECRET_KEY",
2146
+ value: "",
2147
+ condition: hasClerk
2148
+ },
2149
+ {
2150
+ key: "CLERK_PUBLISHABLE_KEY",
2151
+ value: "",
2152
+ condition: needsClerkPublishableKey
2153
+ },
2066
2154
  {
2067
2155
  key: "POLAR_ACCESS_TOKEN",
2068
2156
  value: "",
@@ -2091,7 +2179,7 @@ function buildServerVars(backend, frontend, auth, database, dbSetup, runtime, we
2091
2179
  ];
2092
2180
  }
2093
2181
  function processEnvVariables(vfs, config) {
2094
- const { backend, frontend, database, auth, examples, dbSetup, webDeploy, serverDeploy, runtime, payments } = config;
2182
+ const { backend, frontend, database, auth, api, examples, dbSetup, webDeploy, serverDeploy, runtime, payments } = config;
2095
2183
  const hasReactRouter = frontend.includes("react-router");
2096
2184
  const hasTanStackRouter = frontend.includes("tanstack-router");
2097
2185
  const hasTanStackStart = frontend.includes("tanstack-start");
@@ -2128,7 +2216,7 @@ function processEnvVariables(vfs, config) {
2128
2216
  }
2129
2217
  return;
2130
2218
  }
2131
- const serverVars = buildServerVars(backend, frontend, auth, database, dbSetup, runtime, webDeploy, serverDeploy, payments, examples);
2219
+ const serverVars = buildServerVars(backend, frontend, auth, api, database, dbSetup, runtime, webDeploy, serverDeploy, payments, examples);
2132
2220
  if (backend === "self") {
2133
2221
  const webDir = "apps/web";
2134
2222
  if (vfs.directoryExists(webDir)) writeEnvFile(vfs, `${webDir}/.env`, serverVars);
@@ -2448,6 +2536,77 @@ function getDesktopStaticBuildNote(frontend) {
2448
2536
  if (!staticBuildFrontend) return "";
2449
2537
  return `Desktop builds package static web assets. ${staticBuildFrontends.get(staticBuildFrontend)} needs a static/export build configuration before desktop packaging will work.`;
2450
2538
  }
2539
+ function getClerkQuickstartUrl(frontend) {
2540
+ if (frontend.includes("next")) return "https://clerk.com/docs/nextjs/getting-started/quickstart";
2541
+ if (frontend.includes("react-router")) return "https://clerk.com/docs/react-router/getting-started/quickstart";
2542
+ if (frontend.includes("tanstack-start")) return "https://clerk.com/docs/tanstack-react-start/getting-started/quickstart";
2543
+ if (frontend.includes("tanstack-router")) return "https://clerk.com/docs/react/getting-started/quickstart";
2544
+ if (frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles")) return "https://clerk.com/docs/expo/getting-started/quickstart";
2545
+ return "https://clerk.com/docs";
2546
+ }
2547
+ function getClerkFrontendEnvLines(frontend) {
2548
+ const lines = [];
2549
+ if (frontend.includes("next")) lines.push("- Set `NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY` in `apps/web/.env`");
2550
+ if (frontend.some((value) => [
2551
+ "react-router",
2552
+ "tanstack-router",
2553
+ "tanstack-start"
2554
+ ].includes(value))) lines.push("- Set `VITE_CLERK_PUBLISHABLE_KEY` in `apps/web/.env`");
2555
+ if (frontend.some((value) => [
2556
+ "native-bare",
2557
+ "native-uniwind",
2558
+ "native-unistyles"
2559
+ ].includes(value))) lines.push("- Set `EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY` in `apps/native/.env`");
2560
+ return lines;
2561
+ }
2562
+ function getClerkSetupLines(frontend, backend, api, isConvex) {
2563
+ const lines = getClerkFrontendEnvLines(frontend);
2564
+ const hasClerkServerFrontend = frontend.some((value) => [
2565
+ "next",
2566
+ "react-router",
2567
+ "tanstack-start"
2568
+ ].includes(value));
2569
+ if (isConvex) return [
2570
+ "- Set `CLERK_JWT_ISSUER_DOMAIN` in Convex Dashboard",
2571
+ ...lines,
2572
+ ...hasClerkServerFrontend ? ["- Set `CLERK_SECRET_KEY` in `apps/web/.env` for Clerk server middleware"] : []
2573
+ ];
2574
+ const serverEnvPath = backend === "self" ? "apps/web/.env" : "apps/server/.env";
2575
+ const needsServerSideClerkAuth = backend !== "none";
2576
+ const needsClerkBackendPublishableKey = ["express", "fastify"].includes(backend);
2577
+ const needsClerkRequestVerification = api !== "none" && [
2578
+ "self",
2579
+ "hono",
2580
+ "elysia"
2581
+ ].includes(backend);
2582
+ if (hasClerkServerFrontend && backend === "self") lines.push("- Set `CLERK_SECRET_KEY` in `apps/web/.env` for Clerk server middleware and server-side Clerk auth");
2583
+ else {
2584
+ if (hasClerkServerFrontend) lines.push("- Set `CLERK_SECRET_KEY` in `apps/web/.env` for Clerk server middleware");
2585
+ if (needsServerSideClerkAuth) lines.push(`- Set \`CLERK_SECRET_KEY\` in \`${serverEnvPath}\` for server-side Clerk auth`);
2586
+ }
2587
+ if (needsClerkRequestVerification) lines.push(`- Set \`CLERK_PUBLISHABLE_KEY\` in \`${serverEnvPath}\` for server-side Clerk request verification`);
2588
+ if (needsClerkBackendPublishableKey) lines.push(`- Set \`CLERK_PUBLISHABLE_KEY\` in \`${serverEnvPath}\` for Clerk backend middleware`);
2589
+ return lines;
2590
+ }
2591
+ function hasNativeFrontend(frontend) {
2592
+ return frontend.some((value) => [
2593
+ "native-bare",
2594
+ "native-uniwind",
2595
+ "native-unistyles"
2596
+ ].includes(value));
2597
+ }
2598
+ function hasWebFrontend(frontend) {
2599
+ return frontend.some((value) => [
2600
+ "tanstack-router",
2601
+ "react-router",
2602
+ "tanstack-start",
2603
+ "next",
2604
+ "svelte",
2605
+ "nuxt",
2606
+ "solid",
2607
+ "astro"
2608
+ ].includes(value));
2609
+ }
2451
2610
  function processReadme(vfs, config) {
2452
2611
  const content = generateReadmeContent(config);
2453
2612
  vfs.writeFile("README.md", content);
@@ -2456,11 +2615,7 @@ function generateReadmeContent(options) {
2456
2615
  const { projectName, packageManager, database, auth, addons = [], orm = "drizzle", runtime = "bun", frontend = ["tanstack-router"], backend = "hono", api = "trpc", webDeploy, serverDeploy } = options;
2457
2616
  const isConvex = backend === "convex";
2458
2617
  const hasReactRouter = frontend.includes("react-router");
2459
- const hasNative = frontend.some((f) => [
2460
- "native-bare",
2461
- "native-uniwind",
2462
- "native-unistyles"
2463
- ].includes(f));
2618
+ const hasNative = hasNativeFrontend(frontend);
2464
2619
  const hasReactWeb = frontend.some((f) => [
2465
2620
  "tanstack-router",
2466
2621
  "react-router",
@@ -2503,8 +2658,12 @@ ${auth === "clerk" ? `
2503
2658
  ### Clerk Authentication Setup
2504
2659
 
2505
2660
  - Follow the guide: [Convex + Clerk](https://docs.convex.dev/auth/clerk)
2506
- - Set \`CLERK_JWT_ISSUER_DOMAIN\` in Convex Dashboard
2507
- - Set \`CLERK_PUBLISHABLE_KEY\` in \`apps/*/.env\`` : ""}` : generateDatabaseSetup(options, packageManagerRunCmd)}
2661
+ ${getClerkSetupLines(frontend, backend, api, true).join("\n")}` : ""}` : generateDatabaseSetup(options, packageManagerRunCmd)}
2662
+ ${!isConvex && auth === "clerk" ? `
2663
+ ## Clerk Authentication Setup
2664
+
2665
+ - Follow the guide: [Clerk Quickstart](${getClerkQuickstartUrl(frontend)})
2666
+ ${getClerkSetupLines(frontend, backend, api, false).join("\n")}` : ""}
2508
2667
 
2509
2668
  Then, run the development server:
2510
2669
 
@@ -2539,7 +2698,10 @@ function generateStackDescription(frontend, backend, api, isConvex) {
2539
2698
  svelte: "SvelteKit",
2540
2699
  nuxt: "Nuxt",
2541
2700
  solid: "SolidJS",
2542
- astro: "Astro"
2701
+ astro: "Astro",
2702
+ "native-bare": "React Native, Expo",
2703
+ "native-uniwind": "React Native, Expo",
2704
+ "native-unistyles": "React Native, Expo"
2543
2705
  };
2544
2706
  for (const fe of frontend) if (frontendMap[fe]) {
2545
2707
  parts.push(frontendMap[fe]);
@@ -2551,9 +2713,9 @@ function generateStackDescription(frontend, backend, api, isConvex) {
2551
2713
  }
2552
2714
  function generateRunningInstructions(frontend, backend, webPort, hasNative, isConvex) {
2553
2715
  const instructions = [];
2554
- const hasFrontend = frontend.length > 0 && !frontend.includes("none");
2716
+ const hasAppWebFrontend = hasWebFrontend(frontend);
2555
2717
  const isBackendSelf = backend === "self";
2556
- if (hasFrontend) {
2718
+ if (hasAppWebFrontend) {
2557
2719
  const desc = isBackendSelf ? "fullstack application" : "web application";
2558
2720
  instructions.push(`Open [http://localhost:${webPort}](http://localhost:${webPort}) in your browser to see the ${desc}.`);
2559
2721
  }
@@ -2596,7 +2758,7 @@ function generateProjectStructure(config) {
2596
2758
  const { projectName, frontend, backend, addons, api, auth, database, orm } = config;
2597
2759
  const isConvex = backend === "convex";
2598
2760
  const structure = [`${projectName}/`, "├── apps/"];
2599
- const hasFrontend = frontend.length > 0 && !frontend.includes("none");
2761
+ const hasAppWebFrontend = hasWebFrontend(frontend);
2600
2762
  const isBackendSelf = backend === "self";
2601
2763
  const hasReactWeb = frontend.some((f) => [
2602
2764
  "tanstack-router",
@@ -2604,13 +2766,9 @@ function generateProjectStructure(config) {
2604
2766
  "tanstack-start",
2605
2767
  "next"
2606
2768
  ].includes(f));
2607
- const hasNative = frontend.some((f) => [
2608
- "native-bare",
2609
- "native-uniwind",
2610
- "native-unistyles"
2611
- ].includes(f));
2769
+ const hasNative = hasNativeFrontend(frontend);
2612
2770
  const hasDbPackage = !isConvex && database !== "none" && orm !== "none";
2613
- if (hasFrontend) {
2771
+ if (hasAppWebFrontend) {
2614
2772
  const frontendTypes = {
2615
2773
  "tanstack-router": "React + TanStack Router",
2616
2774
  "react-router": "React + React Router",
@@ -2643,7 +2801,7 @@ function generateProjectStructure(config) {
2643
2801
  }
2644
2802
  if (!isConvex) {
2645
2803
  if (api !== "none") structure.push("│ ├── api/ # API layer / business logic");
2646
- if (auth !== "none") structure.push("│ ├── auth/ # Authentication configuration & logic");
2804
+ if (auth === "better-auth") structure.push("│ ├── auth/ # Authentication configuration & logic");
2647
2805
  if (hasDbPackage) structure.push("│ └── db/ # Database schema & queries");
2648
2806
  }
2649
2807
  }
@@ -2651,18 +2809,15 @@ function generateProjectStructure(config) {
2651
2809
  }
2652
2810
  function generateFeaturesList(database, auth, addons, orm, runtime, frontend, backend, api) {
2653
2811
  const isConvex = backend === "convex";
2654
- const hasNative = frontend.some((f) => [
2655
- "native-bare",
2656
- "native-uniwind",
2657
- "native-unistyles"
2658
- ].includes(f));
2659
- const hasFrontend = frontend.length > 0 && !frontend.includes("none");
2812
+ const hasNative = hasNativeFrontend(frontend);
2813
+ const hasAppWebFrontend = hasWebFrontend(frontend);
2660
2814
  const hasReactWeb = frontend.some((f) => [
2661
2815
  "tanstack-router",
2662
2816
  "react-router",
2663
2817
  "tanstack-start",
2664
2818
  "next"
2665
2819
  ].includes(f));
2820
+ const usesTailwind = hasAppWebFrontend || frontend.includes("native-uniwind");
2666
2821
  const features = ["- **TypeScript** - For type safety and improved developer experience"];
2667
2822
  const frontendFeatures = {
2668
2823
  "tanstack-router": "- **TanStack Router** - File-based routing with full type safety",
@@ -2679,7 +2834,7 @@ function generateFeaturesList(database, auth, addons, orm, runtime, frontend, ba
2679
2834
  break;
2680
2835
  }
2681
2836
  if (hasNative) features.push("- **React Native** - Build mobile apps using React", "- **Expo** - Tools for React Native development");
2682
- if (hasFrontend) features.push("- **TailwindCSS** - Utility-first CSS for rapid UI development");
2837
+ if (usesTailwind) features.push("- **TailwindCSS** - Utility-first CSS for rapid UI development");
2683
2838
  if (hasReactWeb) features.push("- **Shared UI package** - shadcn/ui primitives live in `packages/ui`");
2684
2839
  const backendFeatures = {
2685
2840
  convex: "- **Convex** - Reactive backend-as-a-service platform",
@@ -3284,7 +3439,7 @@ async function processConfigPackage(vfs, templates, config) {
3284
3439
  processTemplatesFromPrefix(vfs, templates, "packages/config", "packages/config", config);
3285
3440
  }
3286
3441
  async function processEnvPackage(vfs, templates, config) {
3287
- const hasWebFrontend = config.frontend.some((f) => [
3442
+ const hasWebFrontend$1 = config.frontend.some((f) => [
3288
3443
  "tanstack-router",
3289
3444
  "react-router",
3290
3445
  "tanstack-start",
@@ -3299,10 +3454,10 @@ async function processEnvPackage(vfs, templates, config) {
3299
3454
  "native-uniwind",
3300
3455
  "native-unistyles"
3301
3456
  ].includes(f));
3302
- if (!hasWebFrontend && !hasNative && config.backend === "none") return;
3457
+ if (!hasWebFrontend$1 && !hasNative && config.backend === "none") return;
3303
3458
  processSingleTemplate(vfs, templates, "packages/env/package.json", "packages/env/package.json", config);
3304
3459
  processSingleTemplate(vfs, templates, "packages/env/tsconfig.json", "packages/env/tsconfig.json", config);
3305
- if (hasWebFrontend) processSingleTemplate(vfs, templates, "packages/env/src/web.ts", "packages/env/src/web.ts", config);
3460
+ if (hasWebFrontend$1) processSingleTemplate(vfs, templates, "packages/env/src/web.ts", "packages/env/src/web.ts", config);
3306
3461
  if (hasNative) processSingleTemplate(vfs, templates, "packages/env/src/native.ts", "packages/env/src/native.ts", config);
3307
3462
  if (config.backend !== "none" && config.backend !== "convex") processSingleTemplate(vfs, templates, "packages/env/src/server.ts", "packages/env/src/server.ts", config);
3308
3463
  }
@@ -4219,6 +4374,8 @@ import { env } from "@{{projectName}}/env/native";
4219
4374
  {{#if (eq auth "better-auth")}}
4220
4375
  import { authClient } from "@/lib/auth-client";
4221
4376
  import { Platform } from "react-native";
4377
+ {{else if (eq auth "clerk")}}
4378
+ import { getClerkAuthToken } from "@/utils/clerk-auth";
4222
4379
  {{/if}}
4223
4380
 
4224
4381
  export const queryClient = new QueryClient({
@@ -4259,6 +4416,11 @@ export const link = new RPCLink({
4259
4416
  }
4260
4417
  return Object.fromEntries(headers);
4261
4418
  },
4419
+ {{else if (eq auth "clerk")}}
4420
+ headers: async () => {
4421
+ const token = await getClerkAuthToken();
4422
+ return token ? { Authorization: \`Bearer \${token}\` } : {};
4423
+ },
4262
4424
  {{/if}}
4263
4425
  });
4264
4426
 
@@ -4316,22 +4478,64 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
4316
4478
  "devDependencies": {},
4317
4479
  "dependencies": {}
4318
4480
  }`],
4319
- ["api/orpc/server/src/context.ts.hbs", `{{#if (and (eq backend 'self') (includes frontend "next"))}}
4481
+ ["api/orpc/server/src/context.ts.hbs", `{{#if (eq auth "clerk")}}
4482
+ type ClerkContextAuth = {
4483
+ userId: string | null;
4484
+ };
4485
+
4486
+ type ClerkRequestContext = {
4487
+ auth: ClerkContextAuth | null;
4488
+ session: null;
4489
+ };
4490
+
4491
+ function toClerkContextAuth(auth: { userId: string | null } | null): ClerkContextAuth | null {
4492
+ return auth ? { userId: auth.userId } : null;
4493
+ }
4494
+ {{/if}}
4495
+
4496
+ {{#if (and (eq auth "clerk") (or (eq backend 'self') (eq backend 'hono') (eq backend 'elysia')))}}
4497
+ import { createClerkClient } from "@clerk/backend";
4498
+ import { env } from "@{{projectName}}/env/server";
4499
+
4500
+ const clerkClient = createClerkClient({
4501
+ secretKey: env.CLERK_SECRET_KEY,
4502
+ publishableKey: env.CLERK_PUBLISHABLE_KEY,
4503
+ });
4504
+
4505
+ async function authenticateClerkRequest(request: Request): Promise<ClerkContextAuth | null> {
4506
+ const requestState = await clerkClient.authenticateRequest(request, {
4507
+ authorizedParties: [env.CORS_ORIGIN],
4508
+ });
4509
+ return toClerkContextAuth(requestState.toAuth());
4510
+ }
4511
+ {{/if}}
4512
+
4513
+ {{#if (and (eq backend 'self') (includes frontend "next"))}}
4320
4514
  import type { NextRequest } from "next/server";
4321
4515
  {{#if (eq auth "better-auth")}}
4322
4516
  import { auth } from "@{{projectName}}/auth";
4323
4517
  {{/if}}
4324
4518
 
4325
- export async function createContext(req: NextRequest) {
4519
+ export async function createContext(req: NextRequest){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
4326
4520
  {{#if (eq auth "better-auth")}}
4327
- const session = await auth.api.getSession({
4328
- headers: req.headers,
4329
- });
4330
- return {
4331
- session,
4332
- };
4521
+ const session = await auth.api.getSession({
4522
+ headers: req.headers,
4523
+ });
4524
+ return {
4525
+ auth: null,
4526
+ session,
4527
+ };
4528
+ {{else if (eq auth "clerk")}}
4529
+ const clerkAuth = await authenticateClerkRequest(req);
4530
+ return {
4531
+ auth: clerkAuth,
4532
+ session: null,
4533
+ };
4333
4534
  {{else}}
4334
- return {}
4535
+ return {
4536
+ auth: null,
4537
+ session: null,
4538
+ };
4335
4539
  {{/if}}
4336
4540
  }
4337
4541
 
@@ -4340,16 +4544,26 @@ export async function createContext(req: NextRequest) {
4340
4544
  import { auth } from "@{{projectName}}/auth";
4341
4545
  {{/if}}
4342
4546
 
4343
- export async function createContext({ req }: { req: Request }) {
4547
+ export async function createContext({ req }: { req: Request }){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
4344
4548
  {{#if (eq auth "better-auth")}}
4345
4549
  const session = await auth.api.getSession({
4346
4550
  headers: req.headers,
4347
4551
  });
4348
4552
  return {
4553
+ auth: null,
4349
4554
  session,
4350
4555
  };
4556
+ {{else if (eq auth "clerk")}}
4557
+ const clerkAuth = await authenticateClerkRequest(req);
4558
+ return {
4559
+ auth: clerkAuth,
4560
+ session: null,
4561
+ };
4351
4562
  {{else}}
4352
- return {};
4563
+ return {
4564
+ auth: null,
4565
+ session: null,
4566
+ };
4353
4567
  {{/if}}
4354
4568
  }
4355
4569
 
@@ -4359,17 +4573,21 @@ import { auth } from "@{{projectName}}/auth";
4359
4573
  {{/if}}
4360
4574
 
4361
4575
  export type CreateContextOptions = {
4362
- headers: Headers;
4576
+ headers: Headers;
4363
4577
  };
4364
4578
 
4365
4579
  export async function createContext({ headers }: CreateContextOptions) {
4366
4580
  {{#if (eq auth "better-auth")}}
4367
- const session = await auth.api.getSession({ headers });
4368
- return {
4369
- session,
4370
- };
4581
+ const session = await auth.api.getSession({ headers });
4582
+ return {
4583
+ auth: null,
4584
+ session,
4585
+ };
4371
4586
  {{else}}
4372
- return {};
4587
+ return {
4588
+ auth: null,
4589
+ session: null,
4590
+ };
4373
4591
  {{/if}}
4374
4592
  }
4375
4593
 
@@ -4379,17 +4597,21 @@ import { auth } from "@{{projectName}}/auth";
4379
4597
  {{/if}}
4380
4598
 
4381
4599
  export type CreateContextOptions = {
4382
- headers: Headers;
4600
+ headers: Headers;
4383
4601
  };
4384
4602
 
4385
4603
  export async function createContext({ headers }: CreateContextOptions) {
4386
4604
  {{#if (eq auth "better-auth")}}
4387
- const session = await auth.api.getSession({ headers });
4388
- return {
4389
- session,
4390
- };
4605
+ const session = await auth.api.getSession({ headers });
4606
+ return {
4607
+ auth: null,
4608
+ session,
4609
+ };
4391
4610
  {{else}}
4392
- return {};
4611
+ return {
4612
+ auth: null,
4613
+ session: null,
4614
+ };
4393
4615
  {{/if}}
4394
4616
  }
4395
4617
 
@@ -4400,22 +4622,29 @@ import { auth } from "@{{projectName}}/auth";
4400
4622
  {{/if}}
4401
4623
 
4402
4624
  export type CreateContextOptions = {
4403
- context: HonoContext;
4625
+ context: HonoContext;
4404
4626
  };
4405
4627
 
4406
- export async function createContext({ context }: CreateContextOptions) {
4628
+ export async function createContext({ context }: CreateContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
4407
4629
  {{#if (eq auth "better-auth")}}
4408
- const session = await auth.api.getSession({
4409
- headers: context.req.raw.headers,
4410
- });
4411
- return {
4412
- session,
4413
- };
4630
+ const session = await auth.api.getSession({
4631
+ headers: context.req.raw.headers,
4632
+ });
4633
+ return {
4634
+ auth: null,
4635
+ session,
4636
+ };
4637
+ {{else if (eq auth "clerk")}}
4638
+ const clerkAuth = await authenticateClerkRequest(context.req.raw);
4639
+ return {
4640
+ auth: clerkAuth,
4641
+ session: null,
4642
+ };
4414
4643
  {{else}}
4415
- // No auth configured
4416
- return {
4417
- session: null,
4418
- };
4644
+ return {
4645
+ auth: null,
4646
+ session: null,
4647
+ };
4419
4648
  {{/if}}
4420
4649
  }
4421
4650
 
@@ -4426,22 +4655,29 @@ import { auth } from "@{{projectName}}/auth";
4426
4655
  {{/if}}
4427
4656
 
4428
4657
  export type CreateContextOptions = {
4429
- context: ElysiaContext;
4658
+ context: ElysiaContext;
4430
4659
  };
4431
4660
 
4432
- export async function createContext({ context }: CreateContextOptions) {
4661
+ export async function createContext({ context }: CreateContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
4433
4662
  {{#if (eq auth "better-auth")}}
4434
- const session = await auth.api.getSession({
4435
- headers: context.request.headers,
4436
- });
4437
- return {
4438
- session,
4439
- };
4663
+ const session = await auth.api.getSession({
4664
+ headers: context.request.headers,
4665
+ });
4666
+ return {
4667
+ auth: null,
4668
+ session,
4669
+ };
4670
+ {{else if (eq auth "clerk")}}
4671
+ const clerkAuth = await authenticateClerkRequest(context.request);
4672
+ return {
4673
+ auth: clerkAuth,
4674
+ session: null,
4675
+ };
4440
4676
  {{else}}
4441
- // No auth configured
4442
- return {
4443
- session: null,
4444
- };
4677
+ return {
4678
+ auth: null,
4679
+ session: null,
4680
+ };
4445
4681
  {{/if}}
4446
4682
  }
4447
4683
 
@@ -4450,56 +4686,77 @@ import type { Request } from "express";
4450
4686
  {{#if (eq auth "better-auth")}}
4451
4687
  import { fromNodeHeaders } from "better-auth/node";
4452
4688
  import { auth } from "@{{projectName}}/auth";
4689
+ {{else if (eq auth "clerk")}}
4690
+ import { getAuth } from "@clerk/express";
4453
4691
  {{/if}}
4454
4692
 
4455
4693
  interface CreateContextOptions {
4456
4694
  req: Request;
4457
4695
  }
4458
4696
 
4459
- export async function createContext(opts: CreateContextOptions) {
4697
+ export async function createContext(opts: CreateContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
4460
4698
  {{#if (eq auth "better-auth")}}
4461
4699
  const session = await auth.api.getSession({
4462
4700
  headers: fromNodeHeaders(opts.req.headers),
4463
4701
  });
4464
4702
  return {
4703
+ auth: null,
4465
4704
  session,
4466
4705
  };
4706
+ {{else if (eq auth "clerk")}}
4707
+ const clerkAuth = toClerkContextAuth(getAuth(opts.req));
4708
+ return {
4709
+ auth: clerkAuth,
4710
+ session: null,
4711
+ };
4467
4712
  {{else}}
4468
- // No auth configured
4469
4713
  return {
4714
+ auth: null,
4470
4715
  session: null,
4471
4716
  };
4472
4717
  {{/if}}
4473
4718
  }
4474
4719
 
4475
4720
  {{else if (eq backend 'fastify')}}
4476
- import type { IncomingHttpHeaders } from "node:http";
4477
4721
  {{#if (eq auth "better-auth")}}
4722
+ import type { IncomingHttpHeaders } from "node:http";
4478
4723
  import { fromNodeHeaders } from "better-auth/node";
4479
4724
  import { auth } from "@{{projectName}}/auth";
4725
+ {{else if (eq auth "clerk")}}
4726
+ import { getAuth } from "@clerk/fastify";
4727
+ {{else}}
4728
+ import type { IncomingHttpHeaders } from "node:http";
4480
4729
  {{/if}}
4481
4730
 
4482
- export async function createContext(req: IncomingHttpHeaders) {
4731
+ export async function createContext(req: {{#if (eq auth "clerk")}}Parameters<typeof getAuth>[0]{{else}}IncomingHttpHeaders{{/if}}){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
4483
4732
  {{#if (eq auth "better-auth")}}
4484
- const session = await auth.api.getSession({
4485
- headers: fromNodeHeaders(req),
4486
- });
4487
- return {
4488
- session,
4489
- };
4733
+ const session = await auth.api.getSession({
4734
+ headers: fromNodeHeaders(req),
4735
+ });
4736
+ return {
4737
+ auth: null,
4738
+ session,
4739
+ };
4740
+ {{else if (eq auth "clerk")}}
4741
+ const clerkAuth = toClerkContextAuth(getAuth(req));
4742
+ return {
4743
+ auth: clerkAuth,
4744
+ session: null,
4745
+ };
4490
4746
  {{else}}
4491
- // No auth configured
4492
- return {
4493
- session: null,
4494
- };
4747
+ return {
4748
+ auth: null,
4749
+ session: null,
4750
+ };
4495
4751
  {{/if}}
4496
4752
  }
4497
4753
 
4498
4754
  {{else}}
4499
4755
  export async function createContext() {
4500
- return {
4501
- session: null,
4502
- };
4756
+ return {
4757
+ auth: null,
4758
+ session: null,
4759
+ };
4503
4760
  }
4504
4761
  {{/if}}
4505
4762
 
@@ -4512,8 +4769,9 @@ export const o = os.$context<Context>();
4512
4769
 
4513
4770
  export const publicProcedure = o;
4514
4771
 
4515
- {{#if (eq auth "better-auth")}}
4772
+ {{#if (or (eq auth "better-auth") (eq auth "clerk"))}}
4516
4773
  const requireAuth = o.middleware(async ({ context, next }) => {
4774
+ {{#if (eq auth "better-auth")}}
4517
4775
  if (!context.session?.user) {
4518
4776
  throw new ORPCError("UNAUTHORIZED");
4519
4777
  }
@@ -4522,13 +4780,23 @@ const requireAuth = o.middleware(async ({ context, next }) => {
4522
4780
  session: context.session,
4523
4781
  },
4524
4782
  });
4783
+ {{else}}
4784
+ if (!context.auth?.userId) {
4785
+ throw new ORPCError("UNAUTHORIZED");
4786
+ }
4787
+ return next({
4788
+ context: {
4789
+ auth: context.auth,
4790
+ },
4791
+ });
4792
+ {{/if}}
4525
4793
  });
4526
4794
 
4527
4795
  export const protectedProcedure = publicProcedure.use(requireAuth);
4528
4796
  {{/if}}
4529
4797
  `],
4530
4798
  ["api/orpc/server/src/routers/index.ts.hbs", `{{#if (eq api "orpc")}}
4531
- import { {{#if (eq auth "better-auth")}}protectedProcedure, {{/if}}publicProcedure } from "../index";
4799
+ import { {{#if (or (eq auth "better-auth") (eq auth "clerk"))}}protectedProcedure, {{/if}}publicProcedure } from "../index";
4532
4800
  import type { RouterClient } from "@orpc/server";
4533
4801
  {{#if (includes examples "todo")}}
4534
4802
  import { todoRouter } from "./todo";
@@ -4538,11 +4806,15 @@ export const appRouter = {
4538
4806
  healthCheck: publicProcedure.handler(() => {
4539
4807
  return "OK";
4540
4808
  }),
4541
- {{#if (eq auth "better-auth")}}
4809
+ {{#if (or (eq auth "better-auth") (eq auth "clerk"))}}
4542
4810
  privateData: protectedProcedure.handler(({ context }) => {
4543
4811
  return {
4544
4812
  message: "This is private",
4813
+ {{#if (eq auth "better-auth")}}
4545
4814
  user: context.session?.user,
4815
+ {{else}}
4816
+ userId: context.auth?.userId,
4817
+ {{/if}}
4546
4818
  };
4547
4819
  }),
4548
4820
  {{/if}}
@@ -4719,11 +4991,17 @@ import { createContext } from "@{{projectName}}/api/context";
4719
4991
  import type { RouterClient } from "@orpc/server";
4720
4992
  import type { AppRouter } from "@{{projectName}}/api/routers/index";
4721
4993
  import { env } from "@{{projectName}}/env/web";
4994
+ {{#if (eq auth "clerk")}}
4995
+ import { getClerkAuthToken } from "@/utils/clerk-auth";
4996
+ {{/if}}
4722
4997
  {{else}}
4723
4998
  import type { AppRouterClient } from "@{{projectName}}/api/routers/index";
4724
4999
  {{#unless (eq backend "self")}}
4725
5000
  import { env } from "@{{projectName}}/env/web";
4726
5001
  {{/unless}}
5002
+ {{#if (eq auth "clerk")}}
5003
+ import { getClerkAuthToken } from "@/utils/clerk-auth";
5004
+ {{/if}}
4727
5005
  {{/if}}
4728
5006
 
4729
5007
  export const queryClient = new QueryClient({
@@ -4768,6 +5046,12 @@ export const client: RouterClient<typeof appRouter> = getORPCClient();
4768
5046
  {{else if (includes frontend "tanstack-start")}}
4769
5047
  const link = new RPCLink({
4770
5048
  url: \`\${env.VITE_SERVER_URL}/rpc\`,
5049
+ {{#if (eq auth "clerk")}}
5050
+ headers: async () => {
5051
+ const token = await getClerkAuthToken();
5052
+ return token ? { Authorization: \`Bearer \${token}\` } : {};
5053
+ },
5054
+ {{/if}}
4771
5055
  {{#if (eq auth "better-auth")}}
4772
5056
  fetch(url, options) {
4773
5057
  return fetch(url, {
@@ -4792,6 +5076,25 @@ export const link = new RPCLink({
4792
5076
  {{else}}
4793
5077
  url: \`\${env.VITE_SERVER_URL}/rpc\`,
4794
5078
  {{/if}}
5079
+ {{#if (eq auth "clerk")}}
5080
+ headers: async () => {
5081
+ {{#if (includes frontend "next")}}
5082
+ if (typeof window !== "undefined") {
5083
+ const token = await getClerkAuthToken();
5084
+ return token ? { Authorization: \`Bearer \${token}\` } : {};
5085
+ }
5086
+
5087
+ const { auth } = await import("@clerk/nextjs/server");
5088
+ const clerkAuth = await auth();
5089
+ const token = await clerkAuth.getToken();
5090
+
5091
+ return token ? { Authorization: \`Bearer \${token}\` } : {};
5092
+ {{else}}
5093
+ const token = await getClerkAuthToken();
5094
+ return token ? { Authorization: \`Bearer \${token}\` } : {};
5095
+ {{/if}}
5096
+ },
5097
+ {{/if}}
4795
5098
  {{#if (eq auth "better-auth")}}
4796
5099
  fetch(url, options) {
4797
5100
  return fetch(url, {
@@ -4816,7 +5119,6 @@ export const client: AppRouterClient = createORPCClient(link)
4816
5119
  {{/if}}
4817
5120
 
4818
5121
  export const orpc = createTanstackQueryUtils(client)
4819
-
4820
5122
  `],
4821
5123
  ["api/orpc/web/solid/src/utils/orpc.ts.hbs", `import { createORPCClient } from "@orpc/client";
4822
5124
  import { RPCLink } from "@orpc/client/fetch";
@@ -4921,6 +5223,8 @@ export const Route = createFileRoute('/api/trpc/$')({
4921
5223
  ["api/trpc/native/utils/trpc.ts.hbs", `{{#if (eq auth "better-auth")}}
4922
5224
  import { authClient } from "@/lib/auth-client";
4923
5225
  import { Platform } from "react-native";
5226
+ {{else if (eq auth "clerk")}}
5227
+ import { getClerkAuthToken } from "@/utils/clerk-auth";
4924
5228
  {{/if}}
4925
5229
  import { QueryClient } from "@tanstack/react-query";
4926
5230
  import { createTRPCClient, httpBatchLink } from "@trpc/client";
@@ -4958,6 +5262,11 @@ const trpcClient = createTRPCClient<AppRouter>({
4958
5262
  }
4959
5263
  return Object.fromEntries(headers);
4960
5264
  },
5265
+ {{else if (eq auth "clerk")}}
5266
+ headers: async function () {
5267
+ const token = await getClerkAuthToken();
5268
+ return token ? { Authorization: \`Bearer \${token}\` } : {};
5269
+ },
4961
5270
  {{/if}}
4962
5271
  }),
4963
5272
  ],
@@ -5017,25 +5326,64 @@ report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
5017
5326
  "scripts": {},
5018
5327
  "devDependencies": {}
5019
5328
  }`],
5020
- ["api/trpc/server/src/context.ts.hbs", `{{#if (and (eq backend 'self') (includes frontend "next"))}}
5021
- import type { NextRequest } from "next/server";
5329
+ ["api/trpc/server/src/context.ts.hbs", `{{#if (eq auth "clerk")}}
5330
+ type ClerkContextAuth = {
5331
+ userId: string | null;
5332
+ };
5333
+
5334
+ type ClerkRequestContext = {
5335
+ auth: ClerkContextAuth | null;
5336
+ session: null;
5337
+ };
5338
+
5339
+ function toClerkContextAuth(auth: { userId: string | null } | null): ClerkContextAuth | null {
5340
+ return auth ? { userId: auth.userId } : null;
5341
+ }
5342
+ {{/if}}
5343
+
5344
+ {{#if (and (eq auth "clerk") (or (eq backend 'self') (eq backend 'hono') (eq backend 'elysia')))}}
5345
+ import { createClerkClient } from "@clerk/backend";
5346
+ import { env } from "@{{projectName}}/env/server";
5347
+
5348
+ const clerkClient = createClerkClient({
5349
+ secretKey: env.CLERK_SECRET_KEY,
5350
+ publishableKey: env.CLERK_PUBLISHABLE_KEY,
5351
+ });
5352
+
5353
+ async function authenticateClerkRequest(request: Request): Promise<ClerkContextAuth | null> {
5354
+ const requestState = await clerkClient.authenticateRequest(request, {
5355
+ authorizedParties: [env.CORS_ORIGIN],
5356
+ });
5357
+ return toClerkContextAuth(requestState.toAuth());
5358
+ }
5359
+ {{/if}}
5360
+
5361
+ {{#if (and (eq backend 'self') (includes frontend "next"))}}
5362
+ import type { NextRequest } from "next/server";
5022
5363
  {{#if (eq auth "better-auth")}}
5023
5364
  import { auth } from "@{{projectName}}/auth";
5024
5365
  {{/if}}
5025
5366
 
5026
- export async function createContext(req: NextRequest) {
5367
+ export async function createContext(req: NextRequest){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
5027
5368
  {{#if (eq auth "better-auth")}}
5028
- const session = await auth.api.getSession({
5029
- headers: req.headers,
5030
- });
5031
- return {
5032
- session,
5033
- };
5369
+ const session = await auth.api.getSession({
5370
+ headers: req.headers,
5371
+ });
5372
+ return {
5373
+ auth: null,
5374
+ session,
5375
+ };
5376
+ {{else if (eq auth "clerk")}}
5377
+ const clerkAuth = await authenticateClerkRequest(req);
5378
+ return {
5379
+ auth: clerkAuth,
5380
+ session: null,
5381
+ };
5034
5382
  {{else}}
5035
- // No auth configured
5036
- return {
5037
- session: null,
5038
- };
5383
+ return {
5384
+ auth: null,
5385
+ session: null,
5386
+ };
5039
5387
  {{/if}}
5040
5388
  }
5041
5389
 
@@ -5044,17 +5392,24 @@ export async function createContext(req: NextRequest) {
5044
5392
  import { auth } from "@{{projectName}}/auth";
5045
5393
  {{/if}}
5046
5394
 
5047
- export async function createContext({ req }: { req: Request }) {
5395
+ export async function createContext({ req }: { req: Request }){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
5048
5396
  {{#if (eq auth "better-auth")}}
5049
5397
  const session = await auth.api.getSession({
5050
5398
  headers: req.headers,
5051
5399
  });
5052
5400
  return {
5401
+ auth: null,
5053
5402
  session,
5054
5403
  };
5404
+ {{else if (eq auth "clerk")}}
5405
+ const clerkAuth = await authenticateClerkRequest(req);
5406
+ return {
5407
+ auth: clerkAuth,
5408
+ session: null,
5409
+ };
5055
5410
  {{else}}
5056
- // No auth configured
5057
5411
  return {
5412
+ auth: null,
5058
5413
  session: null,
5059
5414
  };
5060
5415
  {{/if}}
@@ -5067,22 +5422,29 @@ import { auth } from "@{{projectName}}/auth";
5067
5422
  {{/if}}
5068
5423
 
5069
5424
  export type CreateContextOptions = {
5070
- context: HonoContext;
5425
+ context: HonoContext;
5071
5426
  };
5072
5427
 
5073
- export async function createContext({ context }: CreateContextOptions) {
5428
+ export async function createContext({ context }: CreateContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
5074
5429
  {{#if (eq auth "better-auth")}}
5075
- const session = await auth.api.getSession({
5076
- headers: context.req.raw.headers,
5077
- });
5078
- return {
5079
- session,
5080
- };
5430
+ const session = await auth.api.getSession({
5431
+ headers: context.req.raw.headers,
5432
+ });
5433
+ return {
5434
+ auth: null,
5435
+ session,
5436
+ };
5437
+ {{else if (eq auth "clerk")}}
5438
+ const clerkAuth = await authenticateClerkRequest(context.req.raw);
5439
+ return {
5440
+ auth: clerkAuth,
5441
+ session: null,
5442
+ };
5081
5443
  {{else}}
5082
- // No auth configured
5083
- return {
5084
- session: null,
5085
- };
5444
+ return {
5445
+ auth: null,
5446
+ session: null,
5447
+ };
5086
5448
  {{/if}}
5087
5449
  }
5088
5450
 
@@ -5093,22 +5455,29 @@ import { auth } from "@{{projectName}}/auth";
5093
5455
  {{/if}}
5094
5456
 
5095
5457
  export type CreateContextOptions = {
5096
- context: ElysiaContext;
5458
+ context: ElysiaContext;
5097
5459
  };
5098
5460
 
5099
- export async function createContext({ context }: CreateContextOptions) {
5461
+ export async function createContext({ context }: CreateContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
5100
5462
  {{#if (eq auth "better-auth")}}
5101
- const session = await auth.api.getSession({
5102
- headers: context.request.headers,
5103
- });
5104
- return {
5105
- session,
5106
- };
5463
+ const session = await auth.api.getSession({
5464
+ headers: context.request.headers,
5465
+ });
5466
+ return {
5467
+ auth: null,
5468
+ session,
5469
+ };
5470
+ {{else if (eq auth "clerk")}}
5471
+ const clerkAuth = await authenticateClerkRequest(context.request);
5472
+ return {
5473
+ auth: clerkAuth,
5474
+ session: null,
5475
+ };
5107
5476
  {{else}}
5108
- // No auth configured
5109
- return {
5110
- session: null,
5111
- };
5477
+ return {
5478
+ auth: null,
5479
+ session: null,
5480
+ };
5112
5481
  {{/if}}
5113
5482
  }
5114
5483
 
@@ -5117,19 +5486,28 @@ import type { CreateExpressContextOptions } from "@trpc/server/adapters/express"
5117
5486
  {{#if (eq auth "better-auth")}}
5118
5487
  import { fromNodeHeaders } from "better-auth/node";
5119
5488
  import { auth } from "@{{projectName}}/auth";
5489
+ {{else if (eq auth "clerk")}}
5490
+ import { getAuth } from "@clerk/express";
5120
5491
  {{/if}}
5121
5492
 
5122
- export async function createContext(opts: CreateExpressContextOptions) {
5493
+ export async function createContext(opts: CreateExpressContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
5123
5494
  {{#if (eq auth "better-auth")}}
5124
5495
  const session = await auth.api.getSession({
5125
5496
  headers: fromNodeHeaders(opts.req.headers),
5126
5497
  });
5127
5498
  return {
5499
+ auth: null,
5128
5500
  session,
5129
5501
  };
5502
+ {{else if (eq auth "clerk")}}
5503
+ const clerkAuth = toClerkContextAuth(getAuth(opts.req));
5504
+ return {
5505
+ auth: clerkAuth,
5506
+ session: null,
5507
+ };
5130
5508
  {{else}}
5131
- // No auth configured
5132
5509
  return {
5510
+ auth: null,
5133
5511
  session: null,
5134
5512
  };
5135
5513
  {{/if}}
@@ -5140,17 +5518,28 @@ import type { CreateFastifyContextOptions } from "@trpc/server/adapters/fastify"
5140
5518
  {{#if (eq auth "better-auth")}}
5141
5519
  import { fromNodeHeaders } from "better-auth/node";
5142
5520
  import { auth } from "@{{projectName}}/auth";
5521
+ {{else if (eq auth "clerk")}}
5522
+ import { getAuth } from "@clerk/fastify";
5143
5523
  {{/if}}
5144
5524
 
5145
- export async function createContext({ req, res }: CreateFastifyContextOptions) {
5525
+ export async function createContext({ req }: CreateFastifyContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
5146
5526
  {{#if (eq auth "better-auth")}}
5147
- const session = await auth.api.getSession({
5148
- headers: fromNodeHeaders(req.headers),
5149
- });
5150
- return { session };
5527
+ const session = await auth.api.getSession({
5528
+ headers: fromNodeHeaders(req.headers),
5529
+ });
5530
+ return {
5531
+ auth: null,
5532
+ session,
5533
+ };
5534
+ {{else if (eq auth "clerk")}}
5535
+ const clerkAuth = toClerkContextAuth(getAuth(req));
5536
+ return {
5537
+ auth: clerkAuth,
5538
+ session: null,
5539
+ };
5151
5540
  {{else}}
5152
- // No auth configured
5153
5541
  return {
5542
+ auth: null,
5154
5543
  session: null,
5155
5544
  };
5156
5545
  {{/if}}
@@ -5158,9 +5547,10 @@ export async function createContext({ req, res }: CreateFastifyContextOptions) {
5158
5547
 
5159
5548
  {{else}}
5160
5549
  export async function createContext() {
5161
- return {
5162
- session: null,
5163
- };
5550
+ return {
5551
+ auth: null,
5552
+ session: null,
5553
+ };
5164
5554
  }
5165
5555
  {{/if}}
5166
5556
 
@@ -5175,8 +5565,9 @@ export const router = t.router;
5175
5565
 
5176
5566
  export const publicProcedure = t.procedure;
5177
5567
 
5178
- {{#if (eq auth "better-auth")}}
5568
+ {{#if (or (eq auth "better-auth") (eq auth "clerk"))}}
5179
5569
  export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
5570
+ {{#if (eq auth "better-auth")}}
5180
5571
  if (!ctx.session) {
5181
5572
  throw new TRPCError({
5182
5573
  code: "UNAUTHORIZED",
@@ -5184,10 +5575,23 @@ export const protectedProcedure = t.procedure.use(({ ctx, next }) => {
5184
5575
  cause: "No session",
5185
5576
  });
5186
5577
  }
5578
+ {{else}}
5579
+ if (!ctx.auth?.userId) {
5580
+ throw new TRPCError({
5581
+ code: "UNAUTHORIZED",
5582
+ message: "Authentication required",
5583
+ cause: "No Clerk userId",
5584
+ });
5585
+ }
5586
+ {{/if}}
5187
5587
  return next({
5188
5588
  ctx: {
5189
5589
  ...ctx,
5590
+ {{#if (eq auth "better-auth")}}
5190
5591
  session: ctx.session,
5592
+ {{else}}
5593
+ auth: ctx.auth,
5594
+ {{/if}}
5191
5595
  },
5192
5596
  });
5193
5597
  });
@@ -5220,7 +5624,7 @@ export type AppRouter = typeof appRouter;
5220
5624
  export type AppRouterClient = RouterClient<typeof appRouter>;
5221
5625
  {{else if (eq api "trpc")}}
5222
5626
  import {
5223
- {{#if (eq auth "better-auth")}}protectedProcedure, {{/if}}publicProcedure,
5627
+ {{#if (or (eq auth "better-auth") (eq auth "clerk"))}}protectedProcedure, {{/if}}publicProcedure,
5224
5628
  router,
5225
5629
  } from "../index";
5226
5630
  {{#if (includes examples "todo")}}
@@ -5231,11 +5635,15 @@ export const appRouter = router({
5231
5635
  healthCheck: publicProcedure.query(() => {
5232
5636
  return "OK";
5233
5637
  }),
5234
- {{#if (eq auth "better-auth")}}
5638
+ {{#if (or (eq auth "better-auth") (eq auth "clerk"))}}
5235
5639
  privateData: protectedProcedure.query(({ ctx }) => {
5236
5640
  return {
5237
5641
  message: "This is private",
5642
+ {{#if (eq auth "better-auth")}}
5238
5643
  user: ctx.session.user,
5644
+ {{else}}
5645
+ userId: ctx.auth.userId,
5646
+ {{/if}}
5239
5647
  };
5240
5648
  }),
5241
5649
  {{/if}}
@@ -5268,6 +5676,9 @@ import { toast } from 'sonner';
5268
5676
  {{#unless (eq backend "self")}}
5269
5677
  import { env } from "@{{projectName}}/env/web";
5270
5678
  {{/unless}}
5679
+ {{#if (eq auth "clerk")}}
5680
+ import { getClerkAuthToken } from "@/utils/clerk-auth";
5681
+ {{/if}}
5271
5682
 
5272
5683
  export const queryClient = new QueryClient({
5273
5684
  queryCache: new QueryCache({
@@ -5290,6 +5701,20 @@ const trpcClient = createTRPCClient<AppRouter>({
5290
5701
  {{else}}
5291
5702
  url: \`\${env.NEXT_PUBLIC_SERVER_URL}/trpc\`,
5292
5703
  {{/if}}
5704
+ {{#if (eq auth "clerk")}}
5705
+ headers: async () => {
5706
+ if (typeof window !== "undefined") {
5707
+ const token = await getClerkAuthToken();
5708
+ return token ? { Authorization: \`Bearer \${token}\` } : {};
5709
+ }
5710
+
5711
+ const { auth } = await import("@clerk/nextjs/server");
5712
+ const clerkAuth = await auth();
5713
+ const token = await clerkAuth.getToken();
5714
+
5715
+ return token ? { Authorization: \`Bearer \${token}\` } : {};
5716
+ },
5717
+ {{/if}}
5293
5718
  {{#if (eq auth "better-auth")}}
5294
5719
  fetch(url, options) {
5295
5720
  return fetch(url, {
@@ -5321,6 +5746,9 @@ import { createTRPCClient, httpBatchLink } from "@trpc/client";
5321
5746
  import { createTRPCOptionsProxy } from "@trpc/tanstack-react-query";
5322
5747
  import { toast } from "sonner";
5323
5748
  import { env } from "@{{projectName}}/env/web";
5749
+ {{#if (eq auth "clerk")}}
5750
+ import { getClerkAuthToken } from "@/utils/clerk-auth";
5751
+ {{/if}}
5324
5752
 
5325
5753
  export const queryClient = new QueryClient({
5326
5754
  queryCache: new QueryCache({
@@ -5339,6 +5767,12 @@ export const trpcClient = createTRPCClient<AppRouter>({
5339
5767
  links: [
5340
5768
  httpBatchLink({
5341
5769
  url: \`\${env.VITE_SERVER_URL}/trpc\`,
5770
+ {{#if (eq auth "clerk")}}
5771
+ headers: async () => {
5772
+ const token = await getClerkAuthToken();
5773
+ return token ? { Authorization: \`Bearer \${token}\` } : {};
5774
+ },
5775
+ {{/if}}
5342
5776
  {{#if (eq auth "better-auth")}}
5343
5777
  fetch(url, options) {
5344
5778
  return fetch(url, {
@@ -14451,10 +14885,14 @@ export const get = query({
14451
14885
  });
14452
14886
  `],
14453
14887
  ["auth/clerk/convex/native/base/app/(auth)/_layout.tsx.hbs", `import { Redirect, Stack } from "expo-router";
14454
- import { useAuth } from "@clerk/clerk-expo";
14888
+ import { useAuth } from "@clerk/expo";
14455
14889
 
14456
14890
  export default function AuthRoutesLayout() {
14457
- const { isSignedIn } = useAuth();
14891
+ const { isLoaded, isSignedIn } = useAuth();
14892
+
14893
+ if (!isLoaded) {
14894
+ return null;
14895
+ }
14458
14896
 
14459
14897
  if (isSignedIn) {
14460
14898
  return <Redirect href={"/"} />;
@@ -14463,189 +14901,480 @@ export default function AuthRoutesLayout() {
14463
14901
  return <Stack />;
14464
14902
  }
14465
14903
  `],
14466
- ["auth/clerk/convex/native/base/app/(auth)/sign-in.tsx.hbs", `import { useSignIn } from "@clerk/clerk-expo";
14467
- import { Link, useRouter } from "expo-router";
14468
- import { Text, TextInput, TouchableOpacity, View } from "react-native";
14904
+ ["auth/clerk/convex/native/base/app/(auth)/sign-in.tsx.hbs", `import { useSignIn } from "@clerk/expo";
14905
+ import { type Href, Link, useRouter } from "expo-router";
14469
14906
  import React from "react";
14907
+ import { Pressable, StyleSheet, Text, TextInput, View } from "react-native";
14908
+
14909
+ function pushDecoratedUrl(router: ReturnType<typeof useRouter>, decorateUrl: (url: string) => string, href: string) {
14910
+ const url = decorateUrl(href);
14911
+ const nextHref = url.startsWith("http") ? new URL(url).pathname : url;
14912
+ router.push(nextHref as Href);
14913
+ }
14470
14914
 
14471
14915
  export default function Page() {
14472
- const { signIn, setActive, isLoaded } = useSignIn();
14916
+ const { signIn, errors, fetchStatus } = useSignIn();
14473
14917
  const router = useRouter();
14474
-
14475
14918
  const [emailAddress, setEmailAddress] = React.useState("");
14476
14919
  const [password, setPassword] = React.useState("");
14920
+ const [code, setCode] = React.useState("");
14921
+ const [statusMessage, setStatusMessage] = React.useState<string | null>(null);
14477
14922
 
14478
- // Handle the submission of the sign-in form
14479
- const onSignInPress = async () => {
14480
- if (!isLoaded) return;
14923
+ const emailCodeFactor = signIn.supportedSecondFactors.find(
14924
+ (factor) => factor.strategy === "email_code",
14925
+ );
14926
+ const requiresEmailCode =
14927
+ signIn.status === "needs_client_trust" ||
14928
+ (signIn.status === "needs_second_factor" && !!emailCodeFactor);
14481
14929
 
14482
- // Start the sign-in process using the email and password provided
14483
- try {
14484
- const signInAttempt = await signIn.create({
14485
- identifier: emailAddress,
14486
- password,
14487
- });
14930
+ const handleSubmit = async () => {
14931
+ setStatusMessage(null);
14932
+
14933
+ const { error } = await signIn.password({
14934
+ emailAddress,
14935
+ password,
14936
+ });
14937
+
14938
+ if (error) {
14939
+ console.error(JSON.stringify(error, null, 2));
14940
+ setStatusMessage(error.longMessage ?? "Unable to sign in. Please try again.");
14941
+ return;
14942
+ }
14488
14943
 
14489
- // If sign-in process is complete, set the created session as active
14490
- // and redirect the user
14491
- if (signInAttempt.status === "complete") {
14492
- await setActive({ session: signInAttempt.createdSessionId });
14493
- router.replace("/");
14944
+ if (signIn.status === "complete") {
14945
+ await signIn.finalize({
14946
+ navigate: ({ session, decorateUrl }) => {
14947
+ if (session?.currentTask) {
14948
+ console.log(session.currentTask);
14949
+ return;
14950
+ }
14951
+
14952
+ pushDecoratedUrl(router, decorateUrl, "/");
14953
+ },
14954
+ });
14955
+ } else if (signIn.status === "needs_second_factor" || signIn.status === "needs_client_trust") {
14956
+ if (emailCodeFactor) {
14957
+ await signIn.mfa.sendEmailCode();
14958
+ setStatusMessage(\`We sent a verification code to \${emailCodeFactor.safeIdentifier}.\`);
14494
14959
  } else {
14495
- // If the status isn't complete, check why. User might need to
14496
- // complete further steps.
14497
- console.error(JSON.stringify(signInAttempt, null, 2));
14960
+ console.error("Second factor is required, but email_code is not available:", signIn);
14961
+ setStatusMessage("A second factor is required, but this screen only supports email codes right now.");
14498
14962
  }
14499
- } catch (err) {
14500
- // See https://clerk.com/docs/custom-flows/error-handling
14501
- // for more info on error handling
14502
- console.error(JSON.stringify(err, null, 2));
14963
+ } else {
14964
+ console.error("Sign-in attempt not complete:", signIn);
14965
+ setStatusMessage("Sign-in could not be completed. Check the logs for more details.");
14966
+ }
14967
+ };
14968
+
14969
+ const handleVerify = async () => {
14970
+ setStatusMessage(null);
14971
+
14972
+ await signIn.mfa.verifyEmailCode({ code });
14973
+
14974
+ if (signIn.status === "complete") {
14975
+ await signIn.finalize({
14976
+ navigate: ({ session, decorateUrl }) => {
14977
+ if (session?.currentTask) {
14978
+ console.log(session.currentTask);
14979
+ return;
14980
+ }
14981
+
14982
+ pushDecoratedUrl(router, decorateUrl, "/");
14983
+ },
14984
+ });
14985
+ } else {
14986
+ console.error("Sign-in attempt not complete:", signIn);
14987
+ setStatusMessage("That code did not complete sign-in. Please try again.");
14503
14988
  }
14504
14989
  };
14505
14990
 
14991
+ if (requiresEmailCode) {
14992
+ return (
14993
+ <View style={styles.container}>
14994
+ <Text style={styles.title}>Verify your account</Text>
14995
+ {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}
14996
+ <TextInput
14997
+ style={styles.input}
14998
+ value={code}
14999
+ placeholder="Enter your verification code"
15000
+ placeholderTextColor="#666666"
15001
+ onChangeText={(value) => setCode(value)}
15002
+ keyboardType="numeric"
15003
+ />
15004
+ {errors.fields.code && <Text style={styles.error}>{errors.fields.code.message}</Text>}
15005
+ <Pressable
15006
+ style={({ pressed }) => [
15007
+ styles.button,
15008
+ fetchStatus === "fetching" && styles.buttonDisabled,
15009
+ pressed && styles.buttonPressed,
15010
+ ]}
15011
+ onPress={handleVerify}
15012
+ disabled={fetchStatus === "fetching"}
15013
+ >
15014
+ <Text style={styles.buttonText}>Verify</Text>
15015
+ </Pressable>
15016
+ <Pressable
15017
+ style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}
15018
+ onPress={() => signIn.mfa.sendEmailCode()}
15019
+ >
15020
+ <Text style={styles.secondaryButtonText}>I need a new code</Text>
15021
+ </Pressable>
15022
+ </View>
15023
+ );
15024
+ }
15025
+
14506
15026
  return (
14507
- <View>
14508
- <Text>Sign in</Text>
15027
+ <View style={styles.container}>
15028
+ <Text style={styles.title}>Sign in</Text>
15029
+ {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}
15030
+ <Text style={styles.label}>Email address</Text>
14509
15031
  <TextInput
15032
+ style={styles.input}
14510
15033
  autoCapitalize="none"
14511
15034
  value={emailAddress}
14512
15035
  placeholder="Enter email"
14513
- onChangeText={(emailAddress) => setEmailAddress(emailAddress)}
15036
+ placeholderTextColor="#666666"
15037
+ onChangeText={(value) => setEmailAddress(value)}
15038
+ keyboardType="email-address"
14514
15039
  />
15040
+ {errors.fields.identifier && <Text style={styles.error}>{errors.fields.identifier.message}</Text>}
15041
+ <Text style={styles.label}>Password</Text>
14515
15042
  <TextInput
15043
+ style={styles.input}
14516
15044
  value={password}
14517
15045
  placeholder="Enter password"
15046
+ placeholderTextColor="#666666"
14518
15047
  secureTextEntry={true}
14519
- onChangeText={(password) => setPassword(password)}
15048
+ onChangeText={(value) => setPassword(value)}
14520
15049
  />
14521
- <TouchableOpacity onPress={onSignInPress}>
14522
- <Text>Continue</Text>
14523
- </TouchableOpacity>
14524
- <View style=\\{{ display: "flex", flexDirection: "row", gap: 3 }}>
14525
- <Text>Don't have an account?</Text>
15050
+ {errors.fields.password && <Text style={styles.error}>{errors.fields.password.message}</Text>}
15051
+ <Pressable
15052
+ style={({ pressed }) => [
15053
+ styles.button,
15054
+ (!emailAddress || !password || fetchStatus === "fetching") && styles.buttonDisabled,
15055
+ pressed && styles.buttonPressed,
15056
+ ]}
15057
+ onPress={handleSubmit}
15058
+ disabled={!emailAddress || !password || fetchStatus === "fetching"}
15059
+ >
15060
+ <Text style={styles.buttonText}>Sign in</Text>
15061
+ </Pressable>
15062
+ <View style={styles.linkContainer}>
15063
+ <Text>Don't have an account? </Text>
14526
15064
  <Link href="/sign-up">
14527
- <Text>Sign up</Text>
15065
+ <Text style={styles.linkText}>Sign up</Text>
14528
15066
  </Link>
14529
15067
  </View>
14530
15068
  </View>
14531
15069
  );
14532
15070
  }
15071
+
15072
+ const styles = StyleSheet.create({
15073
+ container: {
15074
+ flex: 1,
15075
+ padding: 20,
15076
+ gap: 12,
15077
+ },
15078
+ title: {
15079
+ marginBottom: 8,
15080
+ fontSize: 24,
15081
+ fontWeight: "700",
15082
+ },
15083
+ label: {
15084
+ fontWeight: "600",
15085
+ fontSize: 14,
15086
+ },
15087
+ input: {
15088
+ borderWidth: 1,
15089
+ borderColor: "#ccc",
15090
+ borderRadius: 8,
15091
+ padding: 12,
15092
+ fontSize: 16,
15093
+ backgroundColor: "#fff",
15094
+ },
15095
+ button: {
15096
+ backgroundColor: "#0a7ea4",
15097
+ paddingVertical: 12,
15098
+ paddingHorizontal: 24,
15099
+ borderRadius: 8,
15100
+ alignItems: "center",
15101
+ marginTop: 8,
15102
+ },
15103
+ buttonPressed: {
15104
+ opacity: 0.7,
15105
+ },
15106
+ buttonDisabled: {
15107
+ opacity: 0.5,
15108
+ },
15109
+ buttonText: {
15110
+ color: "#fff",
15111
+ fontWeight: "600",
15112
+ },
15113
+ secondaryButton: {
15114
+ paddingVertical: 12,
15115
+ paddingHorizontal: 24,
15116
+ borderRadius: 8,
15117
+ alignItems: "center",
15118
+ marginTop: 8,
15119
+ },
15120
+ secondaryButtonText: {
15121
+ color: "#0a7ea4",
15122
+ fontWeight: "600",
15123
+ },
15124
+ linkContainer: {
15125
+ flexDirection: "row",
15126
+ gap: 4,
15127
+ marginTop: 12,
15128
+ alignItems: "center",
15129
+ },
15130
+ linkText: {
15131
+ color: "#0a7ea4",
15132
+ fontWeight: "600",
15133
+ },
15134
+ error: {
15135
+ color: "#d32f2f",
15136
+ fontSize: 12,
15137
+ marginTop: -8,
15138
+ },
15139
+ helper: {
15140
+ color: "#555555",
15141
+ fontSize: 13,
15142
+ },
15143
+ });
14533
15144
  `],
14534
- ["auth/clerk/convex/native/base/app/(auth)/sign-up.tsx.hbs", `import * as React from "react";
14535
- import { Text, TextInput, TouchableOpacity, View } from "react-native";
14536
- import { useSignUp } from "@clerk/clerk-expo";
14537
- import { Link, useRouter } from "expo-router";
15145
+ ["auth/clerk/convex/native/base/app/(auth)/sign-up.tsx.hbs", `import { useAuth, useSignUp } from "@clerk/expo";
15146
+ import { type Href, Link, useRouter } from "expo-router";
15147
+ import React from "react";
15148
+ import { Pressable, StyleSheet, Text, TextInput, View } from "react-native";
14538
15149
 
14539
- export default function SignUpScreen() {
14540
- const { isLoaded, signUp, setActive } = useSignUp();
14541
- const router = useRouter();
15150
+ function pushDecoratedUrl(router: ReturnType<typeof useRouter>, decorateUrl: (url: string) => string, href: string) {
15151
+ const url = decorateUrl(href);
15152
+ const nextHref = url.startsWith("http") ? new URL(url).pathname : url;
15153
+ router.push(nextHref as Href);
15154
+ }
14542
15155
 
15156
+ export default function Page() {
15157
+ const { signUp, errors, fetchStatus } = useSignUp();
15158
+ const { isSignedIn } = useAuth();
15159
+ const router = useRouter();
14543
15160
  const [emailAddress, setEmailAddress] = React.useState("");
14544
15161
  const [password, setPassword] = React.useState("");
14545
- const [pendingVerification, setPendingVerification] = React.useState(false);
14546
15162
  const [code, setCode] = React.useState("");
15163
+ const [statusMessage, setStatusMessage] = React.useState<string | null>(null);
14547
15164
 
14548
- // Handle submission of sign-up form
14549
- const onSignUpPress = async () => {
14550
- if (!isLoaded) return;
14551
-
14552
- // Start sign-up process using email and password provided
14553
- try {
14554
- await signUp.create({
14555
- emailAddress,
14556
- password,
14557
- });
15165
+ const handleSubmit = async () => {
15166
+ setStatusMessage(null);
14558
15167
 
14559
- // Send user an email with verification code
14560
- await signUp.prepareEmailAddressVerification({ strategy: "email_code" });
15168
+ const { error } = await signUp.password({
15169
+ emailAddress,
15170
+ password,
15171
+ });
14561
15172
 
14562
- // Set 'pendingVerification' to true to display second form
14563
- // and capture OTP code
14564
- setPendingVerification(true);
14565
- } catch (err) {
14566
- // See https://clerk.com/docs/custom-flows/error-handling
14567
- // for more info on error handling
14568
- console.error(JSON.stringify(err, null, 2));
15173
+ if (error) {
15174
+ console.error(JSON.stringify(error, null, 2));
15175
+ setStatusMessage(error.longMessage ?? "Unable to sign up. Please try again.");
15176
+ return;
14569
15177
  }
15178
+
15179
+ await signUp.verifications.sendEmailCode();
15180
+ setStatusMessage(\`We sent a verification code to \${emailAddress}.\`);
14570
15181
  };
14571
15182
 
14572
- // Handle submission of verification form
14573
- const onVerifyPress = async () => {
14574
- if (!isLoaded) return;
15183
+ const handleVerify = async () => {
15184
+ setStatusMessage(null);
14575
15185
 
14576
- try {
14577
- // Use the code the user provided to attempt verification
14578
- const signUpAttempt = await signUp.attemptEmailAddressVerification({
14579
- code,
14580
- });
15186
+ await signUp.verifications.verifyEmailCode({
15187
+ code,
15188
+ });
14581
15189
 
14582
- // If verification was completed, set the session to active
14583
- // and redirect the user
14584
- if (signUpAttempt.status === "complete") {
14585
- await setActive({ session: signUpAttempt.createdSessionId });
14586
- router.replace("/");
14587
- } else {
14588
- // If the status is not complete, check why. User may need to
14589
- // complete further steps.
14590
- console.error(JSON.stringify(signUpAttempt, null, 2));
14591
- }
14592
- } catch (err) {
14593
- // See https://clerk.com/docs/custom-flows/error-handling
14594
- // for more info on error handling
14595
- console.error(JSON.stringify(err, null, 2));
15190
+ if (signUp.status === "complete") {
15191
+ await signUp.finalize({
15192
+ navigate: ({ session, decorateUrl }) => {
15193
+ if (session?.currentTask) {
15194
+ console.log(session.currentTask);
15195
+ return;
15196
+ }
15197
+
15198
+ pushDecoratedUrl(router, decorateUrl, "/");
15199
+ },
15200
+ });
15201
+ } else {
15202
+ console.error("Sign-up attempt not complete:", signUp);
15203
+ setStatusMessage("That code did not complete sign-up. Please try again.");
14596
15204
  }
14597
15205
  };
14598
15206
 
14599
- if (pendingVerification) {
15207
+ if (signUp.status === "complete" || isSignedIn) {
15208
+ return null;
15209
+ }
15210
+
15211
+ if (
15212
+ signUp.status === "missing_requirements" &&
15213
+ signUp.unverifiedFields.includes("email_address") &&
15214
+ signUp.missingFields.length === 0
15215
+ ) {
14600
15216
  return (
14601
- <>
14602
- <Text>Verify your email</Text>
15217
+ <View style={styles.container}>
15218
+ <Text style={styles.title}>Verify your account</Text>
15219
+ {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}
14603
15220
  <TextInput
15221
+ style={styles.input}
14604
15222
  value={code}
14605
15223
  placeholder="Enter your verification code"
14606
- onChangeText={(code) => setCode(code)}
15224
+ placeholderTextColor="#666666"
15225
+ onChangeText={(value) => setCode(value)}
15226
+ keyboardType="numeric"
14607
15227
  />
14608
- <TouchableOpacity onPress={onVerifyPress}>
14609
- <Text>Verify</Text>
14610
- </TouchableOpacity>
14611
- </>
15228
+ {errors.fields.code && <Text style={styles.error}>{errors.fields.code.message}</Text>}
15229
+ <Pressable
15230
+ style={({ pressed }) => [
15231
+ styles.button,
15232
+ fetchStatus === "fetching" && styles.buttonDisabled,
15233
+ pressed && styles.buttonPressed,
15234
+ ]}
15235
+ onPress={handleVerify}
15236
+ disabled={fetchStatus === "fetching"}
15237
+ >
15238
+ <Text style={styles.buttonText}>Verify</Text>
15239
+ </Pressable>
15240
+ <Pressable
15241
+ style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}
15242
+ onPress={() => signUp.verifications.sendEmailCode()}
15243
+ >
15244
+ <Text style={styles.secondaryButtonText}>I need a new code</Text>
15245
+ </Pressable>
15246
+ </View>
14612
15247
  );
14613
15248
  }
14614
15249
 
14615
15250
  return (
14616
- <View>
14617
- <Text>Sign up</Text>
15251
+ <View style={styles.container}>
15252
+ <Text style={styles.title}>Sign up</Text>
15253
+ {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}
15254
+ <Text style={styles.label}>Email address</Text>
14618
15255
  <TextInput
15256
+ style={styles.input}
14619
15257
  autoCapitalize="none"
14620
15258
  value={emailAddress}
14621
15259
  placeholder="Enter email"
14622
- onChangeText={(email) => setEmailAddress(email)}
15260
+ placeholderTextColor="#666666"
15261
+ onChangeText={(value) => setEmailAddress(value)}
15262
+ keyboardType="email-address"
14623
15263
  />
15264
+ {errors.fields.emailAddress && (
15265
+ <Text style={styles.error}>{errors.fields.emailAddress.message}</Text>
15266
+ )}
15267
+ <Text style={styles.label}>Password</Text>
14624
15268
  <TextInput
15269
+ style={styles.input}
14625
15270
  value={password}
14626
15271
  placeholder="Enter password"
15272
+ placeholderTextColor="#666666"
14627
15273
  secureTextEntry={true}
14628
- onChangeText={(password) => setPassword(password)}
15274
+ onChangeText={(value) => setPassword(value)}
14629
15275
  />
14630
- <TouchableOpacity onPress={onSignUpPress}>
14631
- <Text>Continue</Text>
14632
- </TouchableOpacity>
14633
- <View style=\\{{ display: "flex", flexDirection: "row", gap: 3 }}>
14634
- <Text>Already have an account?</Text>
15276
+ {errors.fields.password && <Text style={styles.error}>{errors.fields.password.message}</Text>}
15277
+ <Pressable
15278
+ style={({ pressed }) => [
15279
+ styles.button,
15280
+ (!emailAddress || !password || fetchStatus === "fetching") && styles.buttonDisabled,
15281
+ pressed && styles.buttonPressed,
15282
+ ]}
15283
+ onPress={handleSubmit}
15284
+ disabled={!emailAddress || !password || fetchStatus === "fetching"}
15285
+ >
15286
+ <Text style={styles.buttonText}>Sign up</Text>
15287
+ </Pressable>
15288
+ <View style={styles.linkContainer}>
15289
+ <Text>Already have an account? </Text>
14635
15290
  <Link href="/sign-in">
14636
- <Text>Sign in</Text>
15291
+ <Text style={styles.linkText}>Sign in</Text>
14637
15292
  </Link>
14638
15293
  </View>
15294
+ <View nativeID="clerk-captcha" />
14639
15295
  </View>
14640
15296
  );
14641
15297
  }
14642
- `],
14643
- ["auth/clerk/convex/native/base/components/sign-out-button.tsx.hbs", `import { useClerk } from "@clerk/clerk-expo";
14644
- import { useRouter } from "expo-router";
14645
- import { Text, TouchableOpacity } from "react-native";
14646
15298
 
14647
- export const SignOutButton = () => {
14648
- // Use \`useClerk()\` to access the \`signOut()\` function
15299
+ const styles = StyleSheet.create({
15300
+ container: {
15301
+ flex: 1,
15302
+ padding: 20,
15303
+ gap: 12,
15304
+ },
15305
+ title: {
15306
+ marginBottom: 8,
15307
+ fontSize: 24,
15308
+ fontWeight: "700",
15309
+ },
15310
+ label: {
15311
+ fontWeight: "600",
15312
+ fontSize: 14,
15313
+ },
15314
+ input: {
15315
+ borderWidth: 1,
15316
+ borderColor: "#ccc",
15317
+ borderRadius: 8,
15318
+ padding: 12,
15319
+ fontSize: 16,
15320
+ backgroundColor: "#fff",
15321
+ },
15322
+ button: {
15323
+ backgroundColor: "#0a7ea4",
15324
+ paddingVertical: 12,
15325
+ paddingHorizontal: 24,
15326
+ borderRadius: 8,
15327
+ alignItems: "center",
15328
+ marginTop: 8,
15329
+ },
15330
+ buttonPressed: {
15331
+ opacity: 0.7,
15332
+ },
15333
+ buttonDisabled: {
15334
+ opacity: 0.5,
15335
+ },
15336
+ buttonText: {
15337
+ color: "#fff",
15338
+ fontWeight: "600",
15339
+ },
15340
+ secondaryButton: {
15341
+ paddingVertical: 12,
15342
+ paddingHorizontal: 24,
15343
+ borderRadius: 8,
15344
+ alignItems: "center",
15345
+ marginTop: 8,
15346
+ },
15347
+ secondaryButtonText: {
15348
+ color: "#0a7ea4",
15349
+ fontWeight: "600",
15350
+ },
15351
+ linkContainer: {
15352
+ flexDirection: "row",
15353
+ gap: 4,
15354
+ marginTop: 12,
15355
+ alignItems: "center",
15356
+ },
15357
+ linkText: {
15358
+ color: "#0a7ea4",
15359
+ fontWeight: "600",
15360
+ },
15361
+ error: {
15362
+ color: "#d32f2f",
15363
+ fontSize: 12,
15364
+ marginTop: -8,
15365
+ },
15366
+ helper: {
15367
+ color: "#555555",
15368
+ fontSize: 13,
15369
+ },
15370
+ });
15371
+ `],
15372
+ ["auth/clerk/convex/native/base/components/sign-out-button.tsx.hbs", `import { useClerk } from "@clerk/expo";
15373
+ import { useRouter } from "expo-router";
15374
+ import { Text, TouchableOpacity } from "react-native";
15375
+
15376
+ export const SignOutButton = () => {
15377
+ // Use \`useClerk()\` to access the \`signOut()\` function
14649
15378
  const { signOut } = useClerk();
14650
15379
  const router = useRouter();
14651
15380
 
@@ -14670,35 +15399,757 @@ export const SignOutButton = () => {
14670
15399
  `],
14671
15400
  ["auth/clerk/convex/web/react/next/src/app/dashboard/page.tsx.hbs", `"use client";
14672
15401
 
14673
- import { api } from "@{{projectName}}/backend/convex/_generated/api";
14674
- import { SignInButton, UserButton, useUser } from "@clerk/nextjs";
14675
- import { Authenticated, AuthLoading, Unauthenticated, useQuery } from "convex/react";
15402
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
15403
+ import { SignInButton, UserButton, useUser } from "@clerk/nextjs";
15404
+ import { Authenticated, AuthLoading, Unauthenticated, useQuery } from "convex/react";
15405
+
15406
+ export default function Dashboard() {
15407
+ const user = useUser();
15408
+ const privateData = useQuery(api.privateData.get);
15409
+
15410
+ return (
15411
+ <>
15412
+ <Authenticated>
15413
+ <div>
15414
+ <h1>Dashboard</h1>
15415
+ <p>Welcome {user.user?.fullName}</p>
15416
+ <p>privateData: {privateData?.message}</p>
15417
+ <UserButton />
15418
+ </div>
15419
+ </Authenticated>
15420
+ <Unauthenticated>
15421
+ <SignInButton />
15422
+ </Unauthenticated>
15423
+ <AuthLoading>
15424
+ <div>Loading...</div>
15425
+ </AuthLoading>
15426
+ </>
15427
+ );
15428
+ }
15429
+ `],
15430
+ ["auth/clerk/convex/web/react/next/src/proxy.ts.hbs", `import { clerkMiddleware } from "@clerk/nextjs/server";
15431
+
15432
+ export default clerkMiddleware();
15433
+
15434
+ export const config = {
15435
+ matcher: [
15436
+ // Skip Next.js internals and all static files, unless found in search params
15437
+ "/((?!_next|[^?]*\\\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)",
15438
+ // Always run for API routes
15439
+ "/(api|trpc)(.*)",
15440
+ ],
15441
+ };
15442
+ `],
15443
+ ["auth/clerk/convex/web/react/react-router/src/routes/dashboard.tsx.hbs", `import { SignInButton, UserButton, useUser } from "@clerk/react-router";
15444
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
15445
+ import {
15446
+ Authenticated,
15447
+ AuthLoading,
15448
+ Unauthenticated,
15449
+ useQuery,
15450
+ } from "convex/react";
15451
+
15452
+ export default function Dashboard() {
15453
+ const privateData = useQuery(api.privateData.get);
15454
+ const user = useUser();
15455
+
15456
+ return (
15457
+ <>
15458
+ <Authenticated>
15459
+ <div>
15460
+ <h1>Dashboard</h1>
15461
+ <p>Welcome {user.user?.fullName}</p>
15462
+ <p>privateData: {privateData?.message}</p>
15463
+ <UserButton />
15464
+ </div>
15465
+ </Authenticated>
15466
+ <Unauthenticated>
15467
+ <SignInButton />
15468
+ </Unauthenticated>
15469
+ <AuthLoading>
15470
+ <div>Loading...</div>
15471
+ </AuthLoading>
15472
+ </>
15473
+ );
15474
+ }
15475
+ `],
15476
+ ["auth/clerk/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs", `import { SignInButton, UserButton, useUser } from "@clerk/react";
15477
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
15478
+ import { createFileRoute } from "@tanstack/react-router";
15479
+ import {
15480
+ Authenticated,
15481
+ AuthLoading,
15482
+ Unauthenticated,
15483
+ useQuery,
15484
+ } from "convex/react";
15485
+
15486
+ export const Route = createFileRoute("/dashboard")({
15487
+ component: RouteComponent,
15488
+ });
15489
+
15490
+ function RouteComponent() {
15491
+ const privateData = useQuery(api.privateData.get);
15492
+ const user = useUser()
15493
+
15494
+ return (
15495
+ <>
15496
+ <Authenticated>
15497
+ <div>
15498
+ <h1>Dashboard</h1>
15499
+ <p>Welcome {user.user?.fullName}</p>
15500
+ <p>privateData: {privateData?.message}</p>
15501
+ <UserButton />
15502
+ </div>
15503
+ </Authenticated>
15504
+ <Unauthenticated>
15505
+ <SignInButton />
15506
+ </Unauthenticated>
15507
+ <AuthLoading>
15508
+ <div>Loading...</div>
15509
+ </AuthLoading>
15510
+ </>
15511
+ );
15512
+ }
15513
+ `],
15514
+ ["auth/clerk/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs", `import { SignInButton, UserButton, useUser } from "@clerk/tanstack-react-start";
15515
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
15516
+ import { createFileRoute } from "@tanstack/react-router";
15517
+ import {
15518
+ Authenticated,
15519
+ AuthLoading,
15520
+ Unauthenticated,
15521
+ useQuery,
15522
+ } from "convex/react";
15523
+
15524
+ export const Route = createFileRoute("/dashboard")({
15525
+ component: RouteComponent,
15526
+ });
15527
+
15528
+ function RouteComponent() {
15529
+ const privateData = useQuery(api.privateData.get);
15530
+ const user = useUser();
15531
+
15532
+ return (
15533
+ <>
15534
+ <Authenticated>
15535
+ <div>
15536
+ <h1>Dashboard</h1>
15537
+ <p>Welcome {user.user?.fullName}</p>
15538
+ <p>privateData: {privateData?.message}</p>
15539
+ <UserButton />
15540
+ </div>
15541
+ </Authenticated>
15542
+ <Unauthenticated>
15543
+ <SignInButton />
15544
+ </Unauthenticated>
15545
+ <AuthLoading>
15546
+ <div>Loading...</div>
15547
+ </AuthLoading>
15548
+ </>
15549
+ );
15550
+ }
15551
+ `],
15552
+ ["auth/clerk/convex/web/react/tanstack-start/src/start.ts.hbs", `import { clerkMiddleware } from '@clerk/tanstack-react-start/server'
15553
+ import { createStart } from '@tanstack/react-start'
15554
+
15555
+ export const startInstance = createStart(() => {
15556
+ return {
15557
+ requestMiddleware: [clerkMiddleware()],
15558
+ }
15559
+ })`],
15560
+ ["auth/clerk/native/base/app/(auth)/_layout.tsx.hbs", `import { Redirect, Stack } from "expo-router";
15561
+ import { useAuth } from "@clerk/expo";
15562
+
15563
+ export default function AuthRoutesLayout() {
15564
+ const { isLoaded, isSignedIn } = useAuth();
15565
+
15566
+ if (!isLoaded) {
15567
+ return null;
15568
+ }
15569
+
15570
+ if (isSignedIn) {
15571
+ return <Redirect href={"/"} />;
15572
+ }
15573
+
15574
+ return <Stack />;
15575
+ }
15576
+ `],
15577
+ ["auth/clerk/native/base/app/(auth)/sign-in.tsx.hbs", `import { useSignIn } from "@clerk/expo";
15578
+ import { type Href, Link, useRouter } from "expo-router";
15579
+ import React from "react";
15580
+ import { Pressable, StyleSheet, Text, TextInput, View } from "react-native";
15581
+
15582
+ function pushDecoratedUrl(router: ReturnType<typeof useRouter>, decorateUrl: (url: string) => string, href: string) {
15583
+ const url = decorateUrl(href);
15584
+ const nextHref = url.startsWith("http") ? new URL(url).pathname : url;
15585
+ router.push(nextHref as Href);
15586
+ }
15587
+
15588
+ export default function Page() {
15589
+ const { signIn, errors, fetchStatus } = useSignIn();
15590
+ const router = useRouter();
15591
+ const [emailAddress, setEmailAddress] = React.useState("");
15592
+ const [password, setPassword] = React.useState("");
15593
+ const [code, setCode] = React.useState("");
15594
+ const [statusMessage, setStatusMessage] = React.useState<string | null>(null);
15595
+
15596
+ const emailCodeFactor = signIn.supportedSecondFactors.find(
15597
+ (factor) => factor.strategy === "email_code",
15598
+ );
15599
+ const requiresEmailCode =
15600
+ signIn.status === "needs_client_trust" ||
15601
+ (signIn.status === "needs_second_factor" && !!emailCodeFactor);
15602
+
15603
+ const handleSubmit = async () => {
15604
+ setStatusMessage(null);
15605
+
15606
+ const { error } = await signIn.password({
15607
+ emailAddress,
15608
+ password,
15609
+ });
15610
+
15611
+ if (error) {
15612
+ console.error(JSON.stringify(error, null, 2));
15613
+ setStatusMessage(error.longMessage ?? "Unable to sign in. Please try again.");
15614
+ return;
15615
+ }
15616
+
15617
+ if (signIn.status === "complete") {
15618
+ await signIn.finalize({
15619
+ navigate: ({ session, decorateUrl }) => {
15620
+ if (session?.currentTask) {
15621
+ console.log(session.currentTask);
15622
+ return;
15623
+ }
15624
+
15625
+ pushDecoratedUrl(router, decorateUrl, "/");
15626
+ },
15627
+ });
15628
+ } else if (signIn.status === "needs_second_factor" || signIn.status === "needs_client_trust") {
15629
+ if (emailCodeFactor) {
15630
+ await signIn.mfa.sendEmailCode();
15631
+ setStatusMessage(\`We sent a verification code to \${emailCodeFactor.safeIdentifier}.\`);
15632
+ } else {
15633
+ console.error("Second factor is required, but email_code is not available:", signIn);
15634
+ setStatusMessage("A second factor is required, but this screen only supports email codes right now.");
15635
+ }
15636
+ } else {
15637
+ console.error("Sign-in attempt not complete:", signIn);
15638
+ setStatusMessage("Sign-in could not be completed. Check the logs for more details.");
15639
+ }
15640
+ };
15641
+
15642
+ const handleVerify = async () => {
15643
+ setStatusMessage(null);
15644
+
15645
+ await signIn.mfa.verifyEmailCode({ code });
15646
+
15647
+ if (signIn.status === "complete") {
15648
+ await signIn.finalize({
15649
+ navigate: ({ session, decorateUrl }) => {
15650
+ if (session?.currentTask) {
15651
+ console.log(session.currentTask);
15652
+ return;
15653
+ }
15654
+
15655
+ pushDecoratedUrl(router, decorateUrl, "/");
15656
+ },
15657
+ });
15658
+ } else {
15659
+ console.error("Sign-in attempt not complete:", signIn);
15660
+ setStatusMessage("That code did not complete sign-in. Please try again.");
15661
+ }
15662
+ };
15663
+
15664
+ if (requiresEmailCode) {
15665
+ return (
15666
+ <View style={styles.container}>
15667
+ <Text style={styles.title}>Verify your account</Text>
15668
+ {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}
15669
+ <TextInput
15670
+ style={styles.input}
15671
+ value={code}
15672
+ placeholder="Enter your verification code"
15673
+ placeholderTextColor="#666666"
15674
+ onChangeText={(value) => setCode(value)}
15675
+ keyboardType="numeric"
15676
+ />
15677
+ {errors.fields.code && <Text style={styles.error}>{errors.fields.code.message}</Text>}
15678
+ <Pressable
15679
+ style={({ pressed }) => [
15680
+ styles.button,
15681
+ fetchStatus === "fetching" && styles.buttonDisabled,
15682
+ pressed && styles.buttonPressed,
15683
+ ]}
15684
+ onPress={handleVerify}
15685
+ disabled={fetchStatus === "fetching"}
15686
+ >
15687
+ <Text style={styles.buttonText}>Verify</Text>
15688
+ </Pressable>
15689
+ <Pressable
15690
+ style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}
15691
+ onPress={() => signIn.mfa.sendEmailCode()}
15692
+ >
15693
+ <Text style={styles.secondaryButtonText}>I need a new code</Text>
15694
+ </Pressable>
15695
+ </View>
15696
+ );
15697
+ }
15698
+
15699
+ return (
15700
+ <View style={styles.container}>
15701
+ <Text style={styles.title}>Sign in</Text>
15702
+ {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}
15703
+ <Text style={styles.label}>Email address</Text>
15704
+ <TextInput
15705
+ style={styles.input}
15706
+ autoCapitalize="none"
15707
+ value={emailAddress}
15708
+ placeholder="Enter email"
15709
+ placeholderTextColor="#666666"
15710
+ onChangeText={(value) => setEmailAddress(value)}
15711
+ keyboardType="email-address"
15712
+ />
15713
+ {errors.fields.identifier && <Text style={styles.error}>{errors.fields.identifier.message}</Text>}
15714
+ <Text style={styles.label}>Password</Text>
15715
+ <TextInput
15716
+ style={styles.input}
15717
+ value={password}
15718
+ placeholder="Enter password"
15719
+ placeholderTextColor="#666666"
15720
+ secureTextEntry={true}
15721
+ onChangeText={(value) => setPassword(value)}
15722
+ />
15723
+ {errors.fields.password && <Text style={styles.error}>{errors.fields.password.message}</Text>}
15724
+ <Pressable
15725
+ style={({ pressed }) => [
15726
+ styles.button,
15727
+ (!emailAddress || !password || fetchStatus === "fetching") && styles.buttonDisabled,
15728
+ pressed && styles.buttonPressed,
15729
+ ]}
15730
+ onPress={handleSubmit}
15731
+ disabled={!emailAddress || !password || fetchStatus === "fetching"}
15732
+ >
15733
+ <Text style={styles.buttonText}>Sign in</Text>
15734
+ </Pressable>
15735
+ <View style={styles.linkContainer}>
15736
+ <Text>Don't have an account? </Text>
15737
+ <Link href="/sign-up">
15738
+ <Text style={styles.linkText}>Sign up</Text>
15739
+ </Link>
15740
+ </View>
15741
+ </View>
15742
+ );
15743
+ }
15744
+
15745
+ const styles = StyleSheet.create({
15746
+ container: {
15747
+ flex: 1,
15748
+ padding: 20,
15749
+ gap: 12,
15750
+ },
15751
+ title: {
15752
+ marginBottom: 8,
15753
+ fontSize: 24,
15754
+ fontWeight: "700",
15755
+ },
15756
+ label: {
15757
+ fontWeight: "600",
15758
+ fontSize: 14,
15759
+ },
15760
+ input: {
15761
+ borderWidth: 1,
15762
+ borderColor: "#ccc",
15763
+ borderRadius: 8,
15764
+ padding: 12,
15765
+ fontSize: 16,
15766
+ backgroundColor: "#fff",
15767
+ },
15768
+ button: {
15769
+ backgroundColor: "#0a7ea4",
15770
+ paddingVertical: 12,
15771
+ paddingHorizontal: 24,
15772
+ borderRadius: 8,
15773
+ alignItems: "center",
15774
+ marginTop: 8,
15775
+ },
15776
+ buttonPressed: {
15777
+ opacity: 0.7,
15778
+ },
15779
+ buttonDisabled: {
15780
+ opacity: 0.5,
15781
+ },
15782
+ buttonText: {
15783
+ color: "#fff",
15784
+ fontWeight: "600",
15785
+ },
15786
+ secondaryButton: {
15787
+ paddingVertical: 12,
15788
+ paddingHorizontal: 24,
15789
+ borderRadius: 8,
15790
+ alignItems: "center",
15791
+ marginTop: 8,
15792
+ },
15793
+ secondaryButtonText: {
15794
+ color: "#0a7ea4",
15795
+ fontWeight: "600",
15796
+ },
15797
+ linkContainer: {
15798
+ flexDirection: "row",
15799
+ gap: 4,
15800
+ marginTop: 12,
15801
+ alignItems: "center",
15802
+ },
15803
+ linkText: {
15804
+ color: "#0a7ea4",
15805
+ fontWeight: "600",
15806
+ },
15807
+ error: {
15808
+ color: "#d32f2f",
15809
+ fontSize: 12,
15810
+ marginTop: -8,
15811
+ },
15812
+ helper: {
15813
+ color: "#555555",
15814
+ fontSize: 13,
15815
+ },
15816
+ });
15817
+ `],
15818
+ ["auth/clerk/native/base/app/(auth)/sign-up.tsx.hbs", `import { useAuth, useSignUp } from "@clerk/expo";
15819
+ import { type Href, Link, useRouter } from "expo-router";
15820
+ import React from "react";
15821
+ import { Pressable, StyleSheet, Text, TextInput, View } from "react-native";
15822
+
15823
+ function pushDecoratedUrl(router: ReturnType<typeof useRouter>, decorateUrl: (url: string) => string, href: string) {
15824
+ const url = decorateUrl(href);
15825
+ const nextHref = url.startsWith("http") ? new URL(url).pathname : url;
15826
+ router.push(nextHref as Href);
15827
+ }
15828
+
15829
+ export default function Page() {
15830
+ const { signUp, errors, fetchStatus } = useSignUp();
15831
+ const { isSignedIn } = useAuth();
15832
+ const router = useRouter();
15833
+ const [emailAddress, setEmailAddress] = React.useState("");
15834
+ const [password, setPassword] = React.useState("");
15835
+ const [code, setCode] = React.useState("");
15836
+ const [statusMessage, setStatusMessage] = React.useState<string | null>(null);
15837
+
15838
+ const handleSubmit = async () => {
15839
+ setStatusMessage(null);
15840
+
15841
+ const { error } = await signUp.password({
15842
+ emailAddress,
15843
+ password,
15844
+ });
15845
+
15846
+ if (error) {
15847
+ console.error(JSON.stringify(error, null, 2));
15848
+ setStatusMessage(error.longMessage ?? "Unable to sign up. Please try again.");
15849
+ return;
15850
+ }
15851
+
15852
+ await signUp.verifications.sendEmailCode();
15853
+ setStatusMessage(\`We sent a verification code to \${emailAddress}.\`);
15854
+ };
15855
+
15856
+ const handleVerify = async () => {
15857
+ setStatusMessage(null);
15858
+
15859
+ await signUp.verifications.verifyEmailCode({
15860
+ code,
15861
+ });
15862
+
15863
+ if (signUp.status === "complete") {
15864
+ await signUp.finalize({
15865
+ navigate: ({ session, decorateUrl }) => {
15866
+ if (session?.currentTask) {
15867
+ console.log(session.currentTask);
15868
+ return;
15869
+ }
15870
+
15871
+ pushDecoratedUrl(router, decorateUrl, "/");
15872
+ },
15873
+ });
15874
+ } else {
15875
+ console.error("Sign-up attempt not complete:", signUp);
15876
+ setStatusMessage("That code did not complete sign-up. Please try again.");
15877
+ }
15878
+ };
15879
+
15880
+ if (signUp.status === "complete" || isSignedIn) {
15881
+ return null;
15882
+ }
15883
+
15884
+ if (
15885
+ signUp.status === "missing_requirements" &&
15886
+ signUp.unverifiedFields.includes("email_address") &&
15887
+ signUp.missingFields.length === 0
15888
+ ) {
15889
+ return (
15890
+ <View style={styles.container}>
15891
+ <Text style={styles.title}>Verify your account</Text>
15892
+ {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}
15893
+ <TextInput
15894
+ style={styles.input}
15895
+ value={code}
15896
+ placeholder="Enter your verification code"
15897
+ placeholderTextColor="#666666"
15898
+ onChangeText={(value) => setCode(value)}
15899
+ keyboardType="numeric"
15900
+ />
15901
+ {errors.fields.code && <Text style={styles.error}>{errors.fields.code.message}</Text>}
15902
+ <Pressable
15903
+ style={({ pressed }) => [
15904
+ styles.button,
15905
+ fetchStatus === "fetching" && styles.buttonDisabled,
15906
+ pressed && styles.buttonPressed,
15907
+ ]}
15908
+ onPress={handleVerify}
15909
+ disabled={fetchStatus === "fetching"}
15910
+ >
15911
+ <Text style={styles.buttonText}>Verify</Text>
15912
+ </Pressable>
15913
+ <Pressable
15914
+ style={({ pressed }) => [styles.secondaryButton, pressed && styles.buttonPressed]}
15915
+ onPress={() => signUp.verifications.sendEmailCode()}
15916
+ >
15917
+ <Text style={styles.secondaryButtonText}>I need a new code</Text>
15918
+ </Pressable>
15919
+ </View>
15920
+ );
15921
+ }
15922
+
15923
+ return (
15924
+ <View style={styles.container}>
15925
+ <Text style={styles.title}>Sign up</Text>
15926
+ {statusMessage && <Text style={styles.helper}>{statusMessage}</Text>}
15927
+ <Text style={styles.label}>Email address</Text>
15928
+ <TextInput
15929
+ style={styles.input}
15930
+ autoCapitalize="none"
15931
+ value={emailAddress}
15932
+ placeholder="Enter email"
15933
+ placeholderTextColor="#666666"
15934
+ onChangeText={(value) => setEmailAddress(value)}
15935
+ keyboardType="email-address"
15936
+ />
15937
+ {errors.fields.emailAddress && (
15938
+ <Text style={styles.error}>{errors.fields.emailAddress.message}</Text>
15939
+ )}
15940
+ <Text style={styles.label}>Password</Text>
15941
+ <TextInput
15942
+ style={styles.input}
15943
+ value={password}
15944
+ placeholder="Enter password"
15945
+ placeholderTextColor="#666666"
15946
+ secureTextEntry={true}
15947
+ onChangeText={(value) => setPassword(value)}
15948
+ />
15949
+ {errors.fields.password && <Text style={styles.error}>{errors.fields.password.message}</Text>}
15950
+ <Pressable
15951
+ style={({ pressed }) => [
15952
+ styles.button,
15953
+ (!emailAddress || !password || fetchStatus === "fetching") && styles.buttonDisabled,
15954
+ pressed && styles.buttonPressed,
15955
+ ]}
15956
+ onPress={handleSubmit}
15957
+ disabled={!emailAddress || !password || fetchStatus === "fetching"}
15958
+ >
15959
+ <Text style={styles.buttonText}>Sign up</Text>
15960
+ </Pressable>
15961
+ <View style={styles.linkContainer}>
15962
+ <Text>Already have an account? </Text>
15963
+ <Link href="/sign-in">
15964
+ <Text style={styles.linkText}>Sign in</Text>
15965
+ </Link>
15966
+ </View>
15967
+ <View nativeID="clerk-captcha" />
15968
+ </View>
15969
+ );
15970
+ }
15971
+
15972
+ const styles = StyleSheet.create({
15973
+ container: {
15974
+ flex: 1,
15975
+ padding: 20,
15976
+ gap: 12,
15977
+ },
15978
+ title: {
15979
+ marginBottom: 8,
15980
+ fontSize: 24,
15981
+ fontWeight: "700",
15982
+ },
15983
+ label: {
15984
+ fontWeight: "600",
15985
+ fontSize: 14,
15986
+ },
15987
+ input: {
15988
+ borderWidth: 1,
15989
+ borderColor: "#ccc",
15990
+ borderRadius: 8,
15991
+ padding: 12,
15992
+ fontSize: 16,
15993
+ backgroundColor: "#fff",
15994
+ },
15995
+ button: {
15996
+ backgroundColor: "#0a7ea4",
15997
+ paddingVertical: 12,
15998
+ paddingHorizontal: 24,
15999
+ borderRadius: 8,
16000
+ alignItems: "center",
16001
+ marginTop: 8,
16002
+ },
16003
+ buttonPressed: {
16004
+ opacity: 0.7,
16005
+ },
16006
+ buttonDisabled: {
16007
+ opacity: 0.5,
16008
+ },
16009
+ buttonText: {
16010
+ color: "#fff",
16011
+ fontWeight: "600",
16012
+ },
16013
+ secondaryButton: {
16014
+ paddingVertical: 12,
16015
+ paddingHorizontal: 24,
16016
+ borderRadius: 8,
16017
+ alignItems: "center",
16018
+ marginTop: 8,
16019
+ },
16020
+ secondaryButtonText: {
16021
+ color: "#0a7ea4",
16022
+ fontWeight: "600",
16023
+ },
16024
+ linkContainer: {
16025
+ flexDirection: "row",
16026
+ gap: 4,
16027
+ marginTop: 12,
16028
+ alignItems: "center",
16029
+ },
16030
+ linkText: {
16031
+ color: "#0a7ea4",
16032
+ fontWeight: "600",
16033
+ },
16034
+ error: {
16035
+ color: "#d32f2f",
16036
+ fontSize: 12,
16037
+ marginTop: -8,
16038
+ },
16039
+ helper: {
16040
+ color: "#555555",
16041
+ fontSize: 13,
16042
+ },
16043
+ });
16044
+ `],
16045
+ ["auth/clerk/native/base/components/sign-out-button.tsx.hbs", `import { useClerk } from "@clerk/expo";
16046
+ import { useRouter } from "expo-router";
16047
+ import { Text, TouchableOpacity } from "react-native";
16048
+
16049
+ export const SignOutButton = () => {
16050
+ const { signOut } = useClerk();
16051
+ const router = useRouter();
16052
+
16053
+ const handleSignOut = async () => {
16054
+ try {
16055
+ await signOut();
16056
+ router.replace("/");
16057
+ } catch (err) {
16058
+ console.error(JSON.stringify(err, null, 2));
16059
+ }
16060
+ };
16061
+
16062
+ return (
16063
+ <TouchableOpacity onPress={handleSignOut}>
16064
+ <Text>Sign out</Text>
16065
+ </TouchableOpacity>
16066
+ );
16067
+ };
16068
+ `],
16069
+ ["auth/clerk/native/base/utils/clerk-auth.ts.hbs", `type ClerkTokenGetter = () => Promise<string | null>;
16070
+
16071
+ let clerkTokenGetter: ClerkTokenGetter | null = null;
16072
+
16073
+ export function setClerkAuthTokenGetter(getToken: ClerkTokenGetter | null) {
16074
+ clerkTokenGetter = getToken;
16075
+ }
16076
+
16077
+ export async function getClerkAuthToken() {
16078
+ return (await clerkTokenGetter?.()) ?? null;
16079
+ }
16080
+ `],
16081
+ ["auth/clerk/web/react/base/src/utils/clerk-auth.ts.hbs", `type ClerkTokenGetter = () => Promise<string | null>;
16082
+
16083
+ let clerkTokenGetter: ClerkTokenGetter | null = null;
16084
+
16085
+ export function setClerkAuthTokenGetter(getToken: ClerkTokenGetter | null) {
16086
+ clerkTokenGetter = getToken;
16087
+ }
16088
+
16089
+ export async function getClerkAuthToken() {
16090
+ return (await clerkTokenGetter?.()) ?? null;
16091
+ }
16092
+ `],
16093
+ ["auth/clerk/web/react/next/src/app/dashboard/page.tsx.hbs", `"use client";
16094
+
16095
+ {{#if (eq api "orpc")}}
16096
+ import { useQuery } from "@tanstack/react-query";
16097
+ import { orpc } from "@/utils/orpc";
16098
+ {{/if}}
16099
+ {{#if (eq api "trpc")}}
16100
+ import { useQuery } from "@tanstack/react-query";
16101
+ import { trpc } from "@/utils/trpc";
16102
+ {{/if}}
16103
+ import { SignInButton, UserButton, useUser } from "@clerk/nextjs";
16104
+
16105
+ export default function Dashboard() {
16106
+ const user = useUser();
16107
+ const nameFromParts = [user.user?.firstName, user.user?.lastName].filter(Boolean).join(" ");
16108
+ const displayName =
16109
+ user.user?.fullName ||
16110
+ nameFromParts ||
16111
+ user.user?.username ||
16112
+ user.user?.primaryEmailAddress?.emailAddress ||
16113
+ user.user?.primaryPhoneNumber?.phoneNumber ||
16114
+ "User";
16115
+ {{#if (eq api "orpc")}}
16116
+ const privateData = useQuery({
16117
+ ...orpc.privateData.queryOptions(),
16118
+ enabled: user.isLoaded && !!user.user,
16119
+ });
16120
+ {{/if}}
16121
+ {{#if (eq api "trpc")}}
16122
+ const privateData = useQuery({
16123
+ ...trpc.privateData.queryOptions(),
16124
+ enabled: user.isLoaded && !!user.user,
16125
+ });
16126
+ {{/if}}
16127
+
16128
+ if (!user.isLoaded) {
16129
+ return <div className="p-6">Loading...</div>;
16130
+ }
14676
16131
 
14677
- export default function Dashboard() {
14678
- const user = useUser();
14679
- const privateData = useQuery(api.privateData.get);
16132
+ if (!user.user) {
16133
+ return (
16134
+ <div className="p-6">
16135
+ <SignInButton />
16136
+ </div>
16137
+ );
16138
+ }
14680
16139
 
14681
16140
  return (
14682
- <>
14683
- <Authenticated>
14684
- <div>
14685
- <h1>Dashboard</h1>
14686
- <p>Welcome {user.user?.fullName}</p>
14687
- <p>privateData: {privateData?.message}</p>
14688
- <UserButton />
14689
- </div>
14690
- </Authenticated>
14691
- <Unauthenticated>
14692
- <SignInButton />
14693
- </Unauthenticated>
14694
- <AuthLoading>
14695
- <div>Loading...</div>
14696
- </AuthLoading>
14697
- </>
16141
+ <div className="space-y-4 p-6">
16142
+ <h1 className="text-2xl font-semibold">Dashboard</h1>
16143
+ <p>Welcome {displayName}</p>
16144
+ {{#if (or (eq api "orpc") (eq api "trpc"))}}
16145
+ <p>API: {privateData.data?.message}</p>
16146
+ {{/if}}
16147
+ <UserButton />
16148
+ </div>
14698
16149
  );
14699
16150
  }
14700
16151
  `],
14701
- ["auth/clerk/convex/web/react/next/src/middleware.ts.hbs", `import { clerkMiddleware } from "@clerk/nextjs/server";
16152
+ ["auth/clerk/web/react/next/src/proxy.ts.hbs", `import { clerkMiddleware } from "@clerk/nextjs/server";
14702
16153
 
14703
16154
  export default clerkMiddleware();
14704
16155
 
@@ -14711,123 +16162,197 @@ export const config = {
14711
16162
  ],
14712
16163
  };
14713
16164
  `],
14714
- ["auth/clerk/convex/web/react/react-router/src/routes/dashboard.tsx.hbs", `import { SignInButton, UserButton, useUser } from "@clerk/clerk-react";
14715
- import { api } from "@{{projectName}}/backend/convex/_generated/api";
14716
- import {
14717
- Authenticated,
14718
- AuthLoading,
14719
- Unauthenticated,
14720
- useQuery,
14721
- } from "convex/react";
16165
+ ["auth/clerk/web/react/react-router/src/routes/dashboard.tsx.hbs", `{{#if (eq api "orpc")}}
16166
+ import { useQuery } from "@tanstack/react-query";
16167
+ import { orpc } from "@/utils/orpc";
16168
+ {{/if}}
16169
+ {{#if (eq api "trpc")}}
16170
+ import { useQuery } from "@tanstack/react-query";
16171
+ import { trpc } from "@/utils/trpc";
16172
+ {{/if}}
16173
+ import { SignInButton, UserButton, useUser } from "@clerk/react-router";
14722
16174
 
14723
16175
  export default function Dashboard() {
14724
- const privateData = useQuery(api.privateData.get);
14725
- const user = useUser();
16176
+ const user = useUser();
16177
+ const nameFromParts = [user.user?.firstName, user.user?.lastName].filter(Boolean).join(" ");
16178
+ const displayName =
16179
+ user.user?.fullName ||
16180
+ nameFromParts ||
16181
+ user.user?.username ||
16182
+ user.user?.primaryEmailAddress?.emailAddress ||
16183
+ user.user?.primaryPhoneNumber?.phoneNumber ||
16184
+ "User";
16185
+ {{#if (eq api "orpc")}}
16186
+ const privateData = useQuery({
16187
+ ...orpc.privateData.queryOptions(),
16188
+ enabled: user.isLoaded && !!user.user,
16189
+ });
16190
+ {{/if}}
16191
+ {{#if (eq api "trpc")}}
16192
+ const privateData = useQuery({
16193
+ ...trpc.privateData.queryOptions(),
16194
+ enabled: user.isLoaded && !!user.user,
16195
+ });
16196
+ {{/if}}
14726
16197
 
14727
- return (
14728
- <>
14729
- <Authenticated>
14730
- <div>
14731
- <h1>Dashboard</h1>
14732
- <p>Welcome {user.user?.fullName}</p>
14733
- <p>privateData: {privateData?.message}</p>
14734
- <UserButton />
14735
- </div>
14736
- </Authenticated>
14737
- <Unauthenticated>
14738
- <SignInButton />
14739
- </Unauthenticated>
14740
- <AuthLoading>
14741
- <div>Loading...</div>
14742
- </AuthLoading>
14743
- </>
14744
- );
16198
+ if (!user.isLoaded) {
16199
+ return <div className="p-6">Loading...</div>;
16200
+ }
16201
+
16202
+ if (!user.user) {
16203
+ return (
16204
+ <div className="p-6">
16205
+ <SignInButton />
16206
+ </div>
16207
+ );
16208
+ }
16209
+
16210
+ return (
16211
+ <div className="space-y-4 p-6">
16212
+ <h1 className="text-2xl font-semibold">Dashboard</h1>
16213
+ <p>Welcome {displayName}</p>
16214
+ {{#if (or (eq api "orpc") (eq api "trpc"))}}
16215
+ <p>API: {privateData.data?.message}</p>
16216
+ {{/if}}
16217
+ <UserButton />
16218
+ </div>
16219
+ );
14745
16220
  }
14746
16221
  `],
14747
- ["auth/clerk/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs", `import { SignInButton, UserButton, useUser } from "@clerk/clerk-react";
14748
- import { api } from "@{{projectName}}/backend/convex/_generated/api";
16222
+ ["auth/clerk/web/react/tanstack-router/src/routes/dashboard.tsx.hbs", `{{#if (eq api "orpc")}}
16223
+ import { useQuery } from "@tanstack/react-query";
16224
+ import { orpc } from "@/utils/orpc";
16225
+ {{/if}}
16226
+ {{#if (eq api "trpc")}}
16227
+ import { useQuery } from "@tanstack/react-query";
16228
+ import { trpc } from "@/utils/trpc";
16229
+ {{/if}}
16230
+ import { SignInButton, UserButton, useUser } from "@clerk/react";
14749
16231
  import { createFileRoute } from "@tanstack/react-router";
14750
- import {
14751
- Authenticated,
14752
- AuthLoading,
14753
- Unauthenticated,
14754
- useQuery,
14755
- } from "convex/react";
14756
16232
 
14757
16233
  export const Route = createFileRoute("/dashboard")({
14758
16234
  component: RouteComponent,
14759
16235
  });
14760
16236
 
14761
16237
  function RouteComponent() {
14762
- const privateData = useQuery(api.privateData.get);
14763
- const user = useUser()
16238
+ const user = useUser();
16239
+ const nameFromParts = [user.user?.firstName, user.user?.lastName].filter(Boolean).join(" ");
16240
+ const displayName =
16241
+ user.user?.fullName ||
16242
+ nameFromParts ||
16243
+ user.user?.username ||
16244
+ user.user?.primaryEmailAddress?.emailAddress ||
16245
+ user.user?.primaryPhoneNumber?.phoneNumber ||
16246
+ "User";
16247
+ {{#if (eq api "orpc")}}
16248
+ const privateData = useQuery({
16249
+ ...orpc.privateData.queryOptions(),
16250
+ enabled: user.isLoaded && !!user.user,
16251
+ });
16252
+ {{/if}}
16253
+ {{#if (eq api "trpc")}}
16254
+ const privateData = useQuery({
16255
+ ...trpc.privateData.queryOptions(),
16256
+ enabled: user.isLoaded && !!user.user,
16257
+ });
16258
+ {{/if}}
14764
16259
 
14765
- return (
14766
- <>
14767
- <Authenticated>
14768
- <div>
14769
- <h1>Dashboard</h1>
14770
- <p>Welcome {user.user?.fullName}</p>
14771
- <p>privateData: {privateData?.message}</p>
14772
- <UserButton />
14773
- </div>
14774
- </Authenticated>
14775
- <Unauthenticated>
16260
+ if (!user.isLoaded) {
16261
+ return <div className="p-6">Loading...</div>;
16262
+ }
16263
+
16264
+ if (!user.user) {
16265
+ return (
16266
+ <div className="p-6">
14776
16267
  <SignInButton />
14777
- </Unauthenticated>
14778
- <AuthLoading>
14779
- <div>Loading...</div>
14780
- </AuthLoading>
14781
- </>
16268
+ </div>
16269
+ );
16270
+ }
16271
+
16272
+ return (
16273
+ <div className="space-y-4 p-6">
16274
+ <h1 className="text-2xl font-semibold">Dashboard</h1>
16275
+ <p>Welcome {displayName}</p>
16276
+ {{#if (or (eq api "orpc") (eq api "trpc"))}}
16277
+ <p>API: {privateData.data?.message}</p>
16278
+ {{/if}}
16279
+ <UserButton />
16280
+ </div>
14782
16281
  );
14783
16282
  }
14784
16283
  `],
14785
- ["auth/clerk/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs", `import { SignInButton, UserButton, useUser } from "@clerk/tanstack-react-start";
14786
- import { api } from "@{{projectName}}/backend/convex/_generated/api";
16284
+ ["auth/clerk/web/react/tanstack-start/src/routes/dashboard.tsx.hbs", `{{#if (eq api "trpc")}}
16285
+ import { useTRPC } from "@/utils/trpc";
16286
+ import { useQuery } from "@tanstack/react-query";
16287
+ {{/if}}
16288
+ {{#if (eq api "orpc")}}
16289
+ import { useQuery } from "@tanstack/react-query";
16290
+ import { orpc } from "@/utils/orpc";
16291
+ {{/if}}
16292
+ import { SignInButton, UserButton, useUser } from "@clerk/tanstack-react-start";
14787
16293
  import { createFileRoute } from "@tanstack/react-router";
14788
- import {
14789
- Authenticated,
14790
- AuthLoading,
14791
- Unauthenticated,
14792
- useQuery,
14793
- } from "convex/react";
14794
16294
 
14795
16295
  export const Route = createFileRoute("/dashboard")({
14796
16296
  component: RouteComponent,
14797
16297
  });
14798
16298
 
14799
16299
  function RouteComponent() {
14800
- const privateData = useQuery(api.privateData.get);
14801
16300
  const user = useUser();
16301
+ const nameFromParts = [user.user?.firstName, user.user?.lastName].filter(Boolean).join(" ");
16302
+ const displayName =
16303
+ user.user?.fullName ||
16304
+ nameFromParts ||
16305
+ user.user?.username ||
16306
+ user.user?.primaryEmailAddress?.emailAddress ||
16307
+ user.user?.primaryPhoneNumber?.phoneNumber ||
16308
+ "User";
16309
+ {{#if (eq api "trpc")}}
16310
+ const trpc = useTRPC();
16311
+ const privateData = useQuery({
16312
+ ...trpc.privateData.queryOptions(),
16313
+ enabled: user.isLoaded && !!user.user,
16314
+ });
16315
+ {{/if}}
16316
+ {{#if (eq api "orpc")}}
16317
+ const privateData = useQuery({
16318
+ ...orpc.privateData.queryOptions(),
16319
+ enabled: user.isLoaded && !!user.user,
16320
+ });
16321
+ {{/if}}
14802
16322
 
14803
- return (
14804
- <>
14805
- <Authenticated>
14806
- <div>
14807
- <h1>Dashboard</h1>
14808
- <p>Welcome {user.user?.fullName}</p>
14809
- <p>privateData: {privateData?.message}</p>
14810
- <UserButton />
14811
- </div>
14812
- </Authenticated>
14813
- <Unauthenticated>
16323
+ if (!user.isLoaded) {
16324
+ return <div className="p-6">Loading...</div>;
16325
+ }
16326
+
16327
+ if (!user.user) {
16328
+ return (
16329
+ <div className="p-6">
14814
16330
  <SignInButton />
14815
- </Unauthenticated>
14816
- <AuthLoading>
14817
- <div>Loading...</div>
14818
- </AuthLoading>
14819
- </>
16331
+ </div>
16332
+ );
16333
+ }
16334
+
16335
+ return (
16336
+ <div className="space-y-4 p-6">
16337
+ <h1 className="text-2xl font-semibold">Dashboard</h1>
16338
+ <p>Welcome {displayName}</p>
16339
+ {{#if (or (eq api "orpc") (eq api "trpc"))}}
16340
+ <p>API: {privateData.data?.message}</p>
16341
+ {{/if}}
16342
+ <UserButton />
16343
+ </div>
14820
16344
  );
14821
16345
  }
14822
16346
  `],
14823
- ["auth/clerk/convex/web/react/tanstack-start/src/start.ts.hbs", `import { clerkMiddleware } from '@clerk/tanstack-react-start/server'
16347
+ ["auth/clerk/web/react/tanstack-start/src/start.ts.hbs", `import { clerkMiddleware } from '@clerk/tanstack-react-start/server'
14824
16348
  import { createStart } from '@tanstack/react-start'
14825
16349
 
14826
16350
  export const startInstance = createStart(() => {
14827
16351
  return {
14828
16352
  requestMiddleware: [clerkMiddleware()],
14829
16353
  }
14830
- })`],
16354
+ })
16355
+ `],
14831
16356
  ["backend/convex/packages/backend/_gitignore", `
14832
16357
  .env.local
14833
16358
  `],
@@ -15158,8 +16683,10 @@ const app = new Elysia()
15158
16683
  cors({
15159
16684
  origin: env.CORS_ORIGIN,
15160
16685
  methods: ["GET", "POST", "OPTIONS"],
15161
- {{#if (eq auth "better-auth")}}
16686
+ {{#if (or (eq auth "better-auth") (eq auth "clerk"))}}
15162
16687
  allowedHeaders: ["Content-Type", "Authorization"],
16688
+ {{/if}}
16689
+ {{#if (eq auth "better-auth")}}
15163
16690
  credentials: true,
15164
16691
  {{/if}}
15165
16692
  }),
@@ -15246,7 +16773,7 @@ import { ZodToJsonSchemaConverter } from "@orpc/zod/zod4";
15246
16773
  import { RPCHandler } from "@orpc/server/node";
15247
16774
  import { onError } from "@orpc/server";
15248
16775
  import { appRouter } from "@{{projectName}}/api/routers/index";
15249
- {{#if (eq auth "better-auth")}}
16776
+ {{#if (or (eq auth "better-auth") (eq auth "clerk"))}}
15250
16777
  import { createContext } from "@{{projectName}}/api/context";
15251
16778
  {{/if}}
15252
16779
  {{/if}}
@@ -15261,6 +16788,9 @@ import { devToolsMiddleware } from "@ai-sdk/devtools";
15261
16788
  import { auth } from "@{{projectName}}/auth";
15262
16789
  import { toNodeHandler } from "better-auth/node";
15263
16790
  {{/if}}
16791
+ {{#if (eq auth "clerk")}}
16792
+ import { clerkMiddleware } from "@clerk/express";
16793
+ {{/if}}
15264
16794
 
15265
16795
  const app = express();
15266
16796
 
@@ -15268,13 +16798,19 @@ app.use(
15268
16798
  cors({
15269
16799
  origin: env.CORS_ORIGIN,
15270
16800
  methods: ["GET", "POST", "OPTIONS"],
15271
- {{#if (eq auth "better-auth")}}
16801
+ {{#if (or (eq auth "better-auth") (eq auth "clerk"))}}
15272
16802
  allowedHeaders: ["Content-Type", "Authorization"],
16803
+ {{/if}}
16804
+ {{#if (eq auth "better-auth")}}
15273
16805
  credentials: true,
15274
16806
  {{/if}}
15275
16807
  })
15276
16808
  );
15277
16809
 
16810
+ {{#if (eq auth "clerk")}}
16811
+ app.use(clerkMiddleware());
16812
+ {{/if}}
16813
+
15278
16814
  {{#if (eq auth "better-auth")}}
15279
16815
  app.all("/api/auth{/*path}", toNodeHandler(auth));
15280
16816
  {{/if}}
@@ -15313,7 +16849,7 @@ const apiHandler = new OpenAPIHandler(appRouter, {
15313
16849
  app.use(async (req, res, next) => {
15314
16850
  const rpcResult = await rpcHandler.handle(req, res, {
15315
16851
  prefix: "/rpc",
15316
- {{#if (eq auth "better-auth")}}
16852
+ {{#if (or (eq auth "better-auth") (eq auth "clerk"))}}
15317
16853
  context: await createContext({ req }),
15318
16854
  {{else}}
15319
16855
  context: {},
@@ -15323,7 +16859,7 @@ app.use(async (req, res, next) => {
15323
16859
 
15324
16860
  const apiResult = await apiHandler.handle(req, res, {
15325
16861
  prefix: "/api-reference",
15326
- {{#if (eq auth "better-auth")}}
16862
+ {{#if (or (eq auth "better-auth") (eq auth "clerk"))}}
15327
16863
  context: await createContext({ req }),
15328
16864
  {{else}}
15329
16865
  context: {},
@@ -15389,6 +16925,9 @@ import { devToolsMiddleware } from "@ai-sdk/devtools";
15389
16925
  {{#if (eq auth "better-auth")}}
15390
16926
  import { auth } from "@{{projectName}}/auth";
15391
16927
  {{/if}}
16928
+ {{#if (eq auth "clerk")}}
16929
+ import { clerkPlugin } from "@clerk/fastify";
16930
+ {{/if}}
15392
16931
 
15393
16932
  const baseCorsConfig = {
15394
16933
  origin: env.CORS_ORIGIN,
@@ -15434,6 +16973,9 @@ const fastify = Fastify({
15434
16973
  {{/if}}
15435
16974
 
15436
16975
  fastify.register(fastifyCors, baseCorsConfig);
16976
+ {{#if (eq auth "clerk")}}
16977
+ fastify.register(clerkPlugin);
16978
+ {{/if}}
15437
16979
 
15438
16980
  {{#if (eq api "orpc")}}
15439
16981
  fastify.register(async (rpcApp) => {
@@ -15444,7 +16986,7 @@ fastify.register(async (rpcApp) => {
15444
16986
 
15445
16987
  rpcApp.all("/rpc/*", async (request, reply) => {
15446
16988
  const { matched } = await rpcHandler.handle(request, reply, {
15447
- context: await createContext(request.headers),
16989
+ context: await createContext({{#if (eq auth "clerk")}}request{{else}}request.headers{{/if}}),
15448
16990
  prefix: "/rpc",
15449
16991
  });
15450
16992
 
@@ -15455,7 +16997,7 @@ fastify.register(async (rpcApp) => {
15455
16997
 
15456
16998
  rpcApp.all("/api-reference/*", async (request, reply) => {
15457
16999
  const { matched } = await apiHandler.handle(request, reply, {
15458
- context: await createContext(request.headers),
17000
+ context: await createContext({{#if (eq auth "clerk")}}request{{else}}request.headers{{/if}}),
15459
17001
  prefix: "/api-reference",
15460
17002
  });
15461
17003
 
@@ -15583,8 +17125,10 @@ app.use(
15583
17125
  cors({
15584
17126
  origin: env.CORS_ORIGIN,
15585
17127
  allowMethods: ["GET", "POST", "OPTIONS"],
15586
- {{#if (eq auth "better-auth")}}
17128
+ {{#if (or (eq auth "better-auth") (eq auth "clerk"))}}
15587
17129
  allowHeaders: ["Content-Type", "Authorization"],
17130
+ {{/if}}
17131
+ {{#if (eq auth "better-auth")}}
15588
17132
  credentials: true,
15589
17133
  {{/if}}
15590
17134
  })
@@ -21374,7 +22918,7 @@ import {
21374
22918
  import { Checkbox } from "@{{projectName}}/ui/components/checkbox";
21375
22919
  import { Input } from "@{{projectName}}/ui/components/input";
21376
22920
  import { Loader2, Trash2 } from "lucide-react";
21377
- import { useState } from "react";
22921
+ import { useState, type FormEvent } from "react";
21378
22922
 
21379
22923
  {{#if (eq backend "convex")}}
21380
22924
  import { useMutation, useQuery } from "convex/react";
@@ -21400,7 +22944,7 @@ export default function TodosPage() {
21400
22944
  const toggleTodoMutation = useMutation(api.todos.toggle);
21401
22945
  const deleteTodoMutation = useMutation(api.todos.deleteTodo);
21402
22946
 
21403
- const handleAddTodo = async (e) => {
22947
+ const handleAddTodo = async (e: FormEvent<HTMLFormElement>) => {
21404
22948
  e.preventDefault();
21405
22949
  const text = newTodoText.trim();
21406
22950
  if (!text) return;
@@ -21459,7 +23003,7 @@ export default function TodosPage() {
21459
23003
  );
21460
23004
  {{/if}}
21461
23005
 
21462
- const handleAddTodo = (e) => {
23006
+ const handleAddTodo = (e: FormEvent<HTMLFormElement>) => {
21463
23007
  e.preventDefault();
21464
23008
  if (newTodoText.trim()) {
21465
23009
  createMutation.mutate({ text: newTodoText });
@@ -21618,7 +23162,7 @@ import {
21618
23162
  import { Checkbox } from "@{{projectName}}/ui/components/checkbox";
21619
23163
  import { Input } from "@{{projectName}}/ui/components/input";
21620
23164
  import { Loader2, Trash2 } from "lucide-react";
21621
- import { useState } from "react";
23165
+ import { useState, type FormEvent } from "react";
21622
23166
 
21623
23167
  {{#if (eq backend "convex")}}
21624
23168
  import { useMutation, useQuery } from "convex/react";
@@ -21643,7 +23187,7 @@ export default function Todos() {
21643
23187
  const toggleTodo = useMutation(api.todos.toggle);
21644
23188
  const deleteTodo = useMutation(api.todos.deleteTodo);
21645
23189
 
21646
- const handleAddTodo = async (e) => {
23190
+ const handleAddTodo = async (e: FormEvent<HTMLFormElement>) => {
21647
23191
  e.preventDefault();
21648
23192
  const text = newTodoText.trim();
21649
23193
  if (!text) return;
@@ -21702,7 +23246,7 @@ export default function Todos() {
21702
23246
  );
21703
23247
  {{/if}}
21704
23248
 
21705
- const handleAddTodo = (e) => {
23249
+ const handleAddTodo = (e: FormEvent<HTMLFormElement>) => {
21706
23250
  e.preventDefault();
21707
23251
  if (newTodoText.trim()) {
21708
23252
  createMutation.mutate({ text: newTodoText });
@@ -21862,7 +23406,7 @@ import { Checkbox } from "@{{projectName}}/ui/components/checkbox";
21862
23406
  import { Input } from "@{{projectName}}/ui/components/input";
21863
23407
  import { createFileRoute } from "@tanstack/react-router";
21864
23408
  import { Loader2, Trash2 } from "lucide-react";
21865
- import { useState } from "react";
23409
+ import { useState, type FormEvent } from "react";
21866
23410
 
21867
23411
  {{#if (eq backend "convex")}}
21868
23412
  import { useMutation, useQuery } from "convex/react";
@@ -21891,7 +23435,7 @@ function TodosRoute() {
21891
23435
  const toggleTodo = useMutation(api.todos.toggle);
21892
23436
  const deleteTodo = useMutation(api.todos.deleteTodo);
21893
23437
 
21894
- const handleAddTodo = async (e) => {
23438
+ const handleAddTodo = async (e: FormEvent<HTMLFormElement>) => {
21895
23439
  e.preventDefault();
21896
23440
  const text = newTodoText.trim();
21897
23441
  if (!text) return;
@@ -21950,7 +23494,7 @@ function TodosRoute() {
21950
23494
  );
21951
23495
  {{/if}}
21952
23496
 
21953
- const handleAddTodo = (e) => {
23497
+ const handleAddTodo = (e: FormEvent<HTMLFormElement>) => {
21954
23498
  e.preventDefault();
21955
23499
  if (newTodoText.trim()) {
21956
23500
  createMutation.mutate({ text: newTodoText });
@@ -22114,7 +23658,7 @@ import { Trash2 } from "lucide-react";
22114
23658
  {{else}}
22115
23659
  import { Loader2, Trash2 } from "lucide-react";
22116
23660
  {{/if}}
22117
- import { useState } from "react";
23661
+ import { useState, type FormEvent } from "react";
22118
23662
 
22119
23663
  {{#if (eq backend "convex")}}
22120
23664
  import { useSuspenseQuery } from "@tanstack/react-query";
@@ -22147,7 +23691,7 @@ function TodosRoute() {
22147
23691
  const toggleTodo = useMutation(api.todos.toggle);
22148
23692
  const removeTodo = useMutation(api.todos.deleteTodo);
22149
23693
 
22150
- const handleAddTodo = async (e) => {
23694
+ const handleAddTodo = async (e: FormEvent<HTMLFormElement>) => {
22151
23695
  e.preventDefault();
22152
23696
  const text = newTodoText.trim();
22153
23697
  if (text) {
@@ -22226,7 +23770,7 @@ function TodosRoute() {
22226
23770
  );
22227
23771
  {{/if}}
22228
23772
 
22229
- const handleAddTodo = (e) => {
23773
+ const handleAddTodo = (e: FormEvent<HTMLFormElement>) => {
22230
23774
  e.preventDefault();
22231
23775
  if (newTodoText.trim()) {
22232
23776
  createMutation.mutate({ text: newTodoText });
@@ -23193,6 +24737,15 @@ web-build/
23193
24737
  ["frontend/native/bare/app/_layout.tsx.hbs", `{{#if (includes examples "ai")}}
23194
24738
  import "@/polyfills";
23195
24739
  {{/if}}
24740
+ {{#if (and (eq auth "clerk") (ne api "none") (ne backend "convex"))}}
24741
+ import { useEffect } from "react";
24742
+ import { setClerkAuthTokenGetter } from "@/utils/clerk-auth";
24743
+ {{/if}}
24744
+ {{#if (and (ne backend "convex") (eq auth "clerk"))}}
24745
+ import { ClerkProvider{{#unless (eq api "none")}}, useAuth{{/unless}} } from "@clerk/expo";
24746
+ import { tokenCache } from "@clerk/expo/token-cache";
24747
+ import { env } from "@{{projectName}}/env/native";
24748
+ {{/if}}
23196
24749
 
23197
24750
  {{#if (eq backend "convex")}}
23198
24751
  {{#if (eq auth "better-auth")}}
@@ -23205,9 +24758,9 @@ import "@/polyfills";
23205
24758
  import { env } from "@{{projectName}}/env/native";
23206
24759
  {{/if}}
23207
24760
  {{#if (eq auth "clerk")}}
23208
- import { ClerkProvider, useAuth } from "@clerk/clerk-expo";
24761
+ import { ClerkProvider, useAuth } from "@clerk/expo";
23209
24762
  import { ConvexProviderWithClerk } from "convex/react-clerk";
23210
- import { tokenCache } from "@clerk/clerk-expo/token-cache";
24763
+ import { tokenCache } from "@clerk/expo/token-cache";
23211
24764
  {{/if}}
23212
24765
  {{else}}
23213
24766
  {{#unless (eq api "none")}}
@@ -23259,6 +24812,22 @@ const styles = StyleSheet.create({
23259
24812
  },
23260
24813
  });
23261
24814
 
24815
+ {{#if (and (eq auth "clerk") (ne api "none") (ne backend "convex"))}}
24816
+ function ClerkApiAuthBridge() {
24817
+ const { getToken } = useAuth();
24818
+
24819
+ useEffect(() => {
24820
+ setClerkAuthTokenGetter(getToken);
24821
+
24822
+ return () => {
24823
+ setClerkAuthTokenGetter(null);
24824
+ };
24825
+ }, [getToken]);
24826
+
24827
+ return null;
24828
+ }
24829
+ {{/if}}
24830
+
23262
24831
  export default function RootLayout() {
23263
24832
  const { isDarkColorScheme } = useColorScheme();
23264
24833
 
@@ -23303,32 +24872,63 @@ export default function RootLayout() {
23303
24872
  </Stack>
23304
24873
  </GestureHandlerRootView>
23305
24874
  </ThemeProvider>
23306
- </ConvexProvider>
24875
+ </ConvexProvider>
24876
+ {{/if}}
24877
+ {{else}}
24878
+ {{#if (eq auth "clerk")}}
24879
+ <ClerkProvider tokenCache={tokenCache} publishableKey={env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}>
24880
+ {{#unless (eq api "none")}}
24881
+ <ClerkApiAuthBridge />
24882
+ <QueryClientProvider client={queryClient}>
24883
+ <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
24884
+ <StatusBar style={isDarkColorScheme ? "light" : "dark"} />
24885
+ <GestureHandlerRootView style={styles.container}>
24886
+ <Stack>
24887
+ <Stack.Screen name="(drawer)" options=\\{{ headerShown: false }} />
24888
+ <Stack.Screen name="(auth)" options=\\{{ headerShown: false }} />
24889
+ <Stack.Screen name="modal" options=\\{{ title: "Modal", presentation: "modal" }} />
24890
+ </Stack>
24891
+ </GestureHandlerRootView>
24892
+ </ThemeProvider>
24893
+ </QueryClientProvider>
24894
+ {{else}}
24895
+ <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
24896
+ <StatusBar style={isDarkColorScheme ? "light" : "dark"} />
24897
+ <GestureHandlerRootView style={styles.container}>
24898
+ <Stack>
24899
+ <Stack.Screen name="(drawer)" options=\\{{ headerShown: false }} />
24900
+ <Stack.Screen name="(auth)" options=\\{{ headerShown: false }} />
24901
+ <Stack.Screen name="modal" options=\\{{ title: "Modal", presentation: "modal" }} />
24902
+ </Stack>
24903
+ </GestureHandlerRootView>
24904
+ </ThemeProvider>
24905
+ {{/unless}}
24906
+ </ClerkProvider>
24907
+ {{else}}
24908
+ {{#unless (eq api "none")}}
24909
+ <QueryClientProvider client={queryClient}>
24910
+ <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
24911
+ <StatusBar style={isDarkColorScheme ? "light" : "dark"} />
24912
+ <GestureHandlerRootView style={styles.container}>
24913
+ <Stack>
24914
+ <Stack.Screen name="(drawer)" options=\\{{ headerShown: false }} />
24915
+ <Stack.Screen name="modal" options=\\{{ title: "Modal", presentation: "modal" }} />
24916
+ </Stack>
24917
+ </GestureHandlerRootView>
24918
+ </ThemeProvider>
24919
+ </QueryClientProvider>
24920
+ {{else}}
24921
+ <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
24922
+ <StatusBar style={isDarkColorScheme ? "light" : "dark"} />
24923
+ <GestureHandlerRootView style={styles.container}>
24924
+ <Stack>
24925
+ <Stack.Screen name="(drawer)" options=\\{{ headerShown: false }} />
24926
+ <Stack.Screen name="modal" options=\\{{ title: "Modal", presentation: "modal" }} />
24927
+ </Stack>
24928
+ </GestureHandlerRootView>
24929
+ </ThemeProvider>
24930
+ {{/unless}}
23307
24931
  {{/if}}
23308
- {{else}}
23309
- {{#unless (eq api "none")}}
23310
- <QueryClientProvider client={queryClient}>
23311
- <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
23312
- <StatusBar style={isDarkColorScheme ? "light" : "dark"} />
23313
- <GestureHandlerRootView style={styles.container}>
23314
- <Stack>
23315
- <Stack.Screen name="(drawer)" options=\\{{ headerShown: false }} />
23316
- <Stack.Screen name="modal" options=\\{{ title: "Modal", presentation: "modal" }} />
23317
- </Stack>
23318
- </GestureHandlerRootView>
23319
- </ThemeProvider>
23320
- </QueryClientProvider>
23321
- {{else}}
23322
- <ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
23323
- <StatusBar style={isDarkColorScheme ? "light" : "dark"} />
23324
- <GestureHandlerRootView style={styles.container}>
23325
- <Stack>
23326
- <Stack.Screen name="(drawer)" options=\\{{ headerShown: false }} />
23327
- <Stack.Screen name="modal" options=\\{{ title: "Modal", presentation: "modal" }} />
23328
- </Stack>
23329
- </GestureHandlerRootView>
23330
- </ThemeProvider>
23331
- {{/unless}}
23332
24932
  {{/if}}
23333
24933
  </>
23334
24934
  );
@@ -23571,7 +25171,11 @@ import { trpc } from "@/utils/trpc";
23571
25171
  import { Link } from "expo-router";
23572
25172
  import { Authenticated, AuthLoading, Unauthenticated, useQuery } from "convex/react";
23573
25173
  import { api } from "@{{ projectName }}/backend/convex/_generated/api";
23574
- import { useUser } from "@clerk/clerk-expo";
25174
+ import { useUser } from "@clerk/expo";
25175
+ import { SignOutButton } from "@/components/sign-out-button";
25176
+ {{else if (and (ne backend "convex") (eq auth "clerk"))}}
25177
+ import { Link } from "expo-router";
25178
+ import { useAuth, useUser } from "@clerk/expo";
23575
25179
  import { SignOutButton } from "@/components/sign-out-button";
23576
25180
  {{else if (and (eq backend "convex") (eq auth "better-auth"))}}
23577
25181
  import { useConvexAuth, useQuery } from "convex/react";
@@ -23597,6 +25201,9 @@ const healthCheck = useQuery(trpc.healthCheck.queryOptions());
23597
25201
  const { user } = useUser();
23598
25202
  const healthCheck = useQuery(api.healthCheck.get);
23599
25203
  const privateData = useQuery(api.privateData.get);
25204
+ {{else if (and (ne backend "convex") (eq auth "clerk"))}}
25205
+ const { isLoaded, isSignedIn } = useAuth();
25206
+ const { user } = useUser();
23600
25207
  {{else if (and (eq backend "convex") (eq auth "better-auth"))}}
23601
25208
  const healthCheck = useQuery(api.healthCheck.get);
23602
25209
  const { isAuthenticated } = useConvexAuth();
@@ -23672,6 +25279,33 @@ return (
23672
25279
  </AuthLoading>
23673
25280
  {{/if}}
23674
25281
 
25282
+ {{#if (and (ne backend "convex") (eq auth "clerk"))}}
25283
+ {!isLoaded ? (
25284
+ <Text style=\\{{ color: theme.text }}>Loading...</Text>
25285
+ ) : isSignedIn ? (
25286
+ <View style={[styles.userCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
25287
+ <View style={styles.userHeader}>
25288
+ <Text style={[styles.userText, { color: theme.text }]}>
25289
+ Welcome, <Text style={styles.userName}>{user?.fullName ?? user?.firstName ?? "there"}</Text>
25290
+ </Text>
25291
+ </View>
25292
+ <Text style={[styles.userEmail, { color: theme.text, opacity: 0.7 }]}>
25293
+ {user?.emailAddresses[0]?.emailAddress}
25294
+ </Text>
25295
+ <SignOutButton />
25296
+ </View>
25297
+ ) : (
25298
+ <View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>
25299
+ <Link href="/(auth)/sign-in">
25300
+ <Text style=\\{{ color: theme.primary }}>Sign in</Text>
25301
+ </Link>
25302
+ <Link href="/(auth)/sign-up">
25303
+ <Text style=\\{{ color: theme.primary }}>Sign up</Text>
25304
+ </Link>
25305
+ </View>
25306
+ )}
25307
+ {{/if}}
25308
+
23675
25309
  {{#if (and (eq backend "convex") (eq auth "better-auth"))}}
23676
25310
  {user ? (
23677
25311
  <View style={[styles.userCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
@@ -23788,7 +25422,8 @@ statusCardTitle: {
23788
25422
  marginBottom: 8,
23789
25423
  fontWeight: "bold",
23790
25424
  },
23791
- });`],
25425
+ });
25426
+ `],
23792
25427
  ["frontend/native/bare/app/+not-found.tsx.hbs", `import { Container } from "@/components/container";
23793
25428
  import { Link, Stack } from "expo-router";
23794
25429
  import { Text, View, StyleSheet } from "react-native";
@@ -24169,6 +25804,15 @@ android
24169
25804
  ["frontend/native/unistyles/app/_layout.tsx.hbs", `{{#if (includes examples "ai")}}
24170
25805
  import "@/polyfills";
24171
25806
  {{/if}}
25807
+ {{#if (and (eq auth "clerk") (ne api "none") (ne backend "convex"))}}
25808
+ import { useEffect } from "react";
25809
+ import { setClerkAuthTokenGetter } from "@/utils/clerk-auth";
25810
+ {{/if}}
25811
+ {{#if (and (ne backend "convex") (eq auth "clerk"))}}
25812
+ import { ClerkProvider{{#unless (eq api "none")}}, useAuth{{/unless}} } from "@clerk/expo";
25813
+ import { tokenCache } from "@clerk/expo/token-cache";
25814
+ import { env } from "@{{projectName}}/env/native";
25815
+ {{/if}}
24172
25816
  {{#if (eq api "trpc")}}
24173
25817
  import { queryClient } from "@/utils/trpc";
24174
25818
  {{/if}}
@@ -24186,9 +25830,9 @@ import { ConvexProvider, ConvexReactClient } from "convex/react";
24186
25830
  import { env } from "@{{projectName}}/env/native";
24187
25831
  {{/if}}
24188
25832
  {{#if (eq auth "clerk")}}
24189
- import { ClerkProvider, useAuth } from "@clerk/clerk-expo";
25833
+ import { ClerkProvider, useAuth } from "@clerk/expo";
24190
25834
  import { ConvexProviderWithClerk } from "convex/react-clerk";
24191
- import { tokenCache } from "@clerk/clerk-expo/token-cache";
25835
+ import { tokenCache } from "@clerk/expo/token-cache";
24192
25836
  {{/if}}
24193
25837
  {{else}}
24194
25838
  {{#unless (eq api "none")}}
@@ -24210,6 +25854,22 @@ const convex = new ConvexReactClient(env.EXPO_PUBLIC_CONVEX_URL, {
24210
25854
  });
24211
25855
  {{/if}}
24212
25856
 
25857
+ {{#if (and (eq auth "clerk") (ne api "none") (ne backend "convex"))}}
25858
+ function ClerkApiAuthBridge() {
25859
+ const { getToken } = useAuth();
25860
+
25861
+ useEffect(() => {
25862
+ setClerkAuthTokenGetter(getToken);
25863
+
25864
+ return () => {
25865
+ setClerkAuthTokenGetter(null);
25866
+ };
25867
+ }, [getToken]);
25868
+
25869
+ return null;
25870
+ }
25871
+ {{/if}}
25872
+
24213
25873
  export default function RootLayout() {
24214
25874
  const { theme } = useUnistyles();
24215
25875
 
@@ -24289,49 +25949,103 @@ export default function RootLayout() {
24289
25949
  </ConvexProvider>
24290
25950
  {{/if}}
24291
25951
  {{else}}
24292
- {{#unless (eq api "none")}}
24293
- <QueryClientProvider client={queryClient}>
24294
- <GestureHandlerRootView style=\\{{ flex: 1 }}>
24295
- <Stack
24296
- screenOptions=\\{{
24297
- headerStyle: {
24298
- backgroundColor: theme.colors.background,
24299
- },
24300
- headerTitleStyle: {
24301
- color: theme.colors.foreground,
24302
- },
24303
- headerTintColor: theme.colors.foreground,
24304
- }}
24305
- >
24306
- <Stack.Screen name="(drawer)" options=\\{{ headerShown: false }} />
24307
- <Stack.Screen
24308
- name="modal"
24309
- options=\\{{ title: "Modal", presentation: "modal" }}
24310
- />
24311
- </Stack>
24312
- </GestureHandlerRootView>
24313
- </QueryClientProvider>
25952
+ {{#if (eq auth "clerk")}}
25953
+ <ClerkProvider
25954
+ tokenCache={tokenCache}
25955
+ publishableKey={env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}
25956
+ >
25957
+ {{#unless (eq api "none")}}
25958
+ <ClerkApiAuthBridge />
25959
+ <QueryClientProvider client={queryClient}>
25960
+ <GestureHandlerRootView style=\\{{ flex: 1 }}>
25961
+ <Stack
25962
+ screenOptions=\\{{
25963
+ headerStyle: {
25964
+ backgroundColor: theme.colors.background,
25965
+ },
25966
+ headerTitleStyle: {
25967
+ color: theme.colors.foreground,
25968
+ },
25969
+ headerTintColor: theme.colors.foreground,
25970
+ }}
25971
+ >
25972
+ <Stack.Screen name="(drawer)" options=\\{{ headerShown: false }} />
25973
+ <Stack.Screen name="(auth)" options=\\{{ headerShown: false }} />
25974
+ <Stack.Screen
25975
+ name="modal"
25976
+ options=\\{{ title: "Modal", presentation: "modal" }}
25977
+ />
25978
+ </Stack>
25979
+ </GestureHandlerRootView>
25980
+ </QueryClientProvider>
25981
+ {{else}}
25982
+ <GestureHandlerRootView style=\\{{ flex: 1 }}>
25983
+ <Stack
25984
+ screenOptions=\\{{
25985
+ headerStyle: {
25986
+ backgroundColor: theme.colors.background,
25987
+ },
25988
+ headerTitleStyle: {
25989
+ color: theme.colors.foreground,
25990
+ },
25991
+ headerTintColor: theme.colors.foreground,
25992
+ }}
25993
+ >
25994
+ <Stack.Screen name="(drawer)" options=\\{{ headerShown: false }} />
25995
+ <Stack.Screen name="(auth)" options=\\{{ headerShown: false }} />
25996
+ <Stack.Screen
25997
+ name="modal"
25998
+ options=\\{{ title: "Modal", presentation: "modal" }}
25999
+ />
26000
+ </Stack>
26001
+ </GestureHandlerRootView>
26002
+ {{/unless}}
26003
+ </ClerkProvider>
24314
26004
  {{else}}
24315
- <GestureHandlerRootView style=\\{{ flex: 1 }}>
24316
- <Stack
24317
- screenOptions=\\{{
24318
- headerStyle: {
24319
- backgroundColor: theme.colors.background,
24320
- },
24321
- headerTitleStyle: {
24322
- color: theme.colors.foreground,
24323
- },
24324
- headerTintColor: theme.colors.foreground,
24325
- }}
24326
- >
24327
- <Stack.Screen name="(drawer)" options=\\{{ headerShown: false }} />
24328
- <Stack.Screen
24329
- name="modal"
24330
- options=\\{{ title: "Modal", presentation: "modal" }}
24331
- />
24332
- </Stack>
24333
- </GestureHandlerRootView>
24334
- {{/unless}}
26005
+ {{#unless (eq api "none")}}
26006
+ <QueryClientProvider client={queryClient}>
26007
+ <GestureHandlerRootView style=\\{{ flex: 1 }}>
26008
+ <Stack
26009
+ screenOptions=\\{{
26010
+ headerStyle: {
26011
+ backgroundColor: theme.colors.background,
26012
+ },
26013
+ headerTitleStyle: {
26014
+ color: theme.colors.foreground,
26015
+ },
26016
+ headerTintColor: theme.colors.foreground,
26017
+ }}
26018
+ >
26019
+ <Stack.Screen name="(drawer)" options=\\{{ headerShown: false }} />
26020
+ <Stack.Screen
26021
+ name="modal"
26022
+ options=\\{{ title: "Modal", presentation: "modal" }}
26023
+ />
26024
+ </Stack>
26025
+ </GestureHandlerRootView>
26026
+ </QueryClientProvider>
26027
+ {{else}}
26028
+ <GestureHandlerRootView style=\\{{ flex: 1 }}>
26029
+ <Stack
26030
+ screenOptions=\\{{
26031
+ headerStyle: {
26032
+ backgroundColor: theme.colors.background,
26033
+ },
26034
+ headerTitleStyle: {
26035
+ color: theme.colors.foreground,
26036
+ },
26037
+ headerTintColor: theme.colors.foreground,
26038
+ }}
26039
+ >
26040
+ <Stack.Screen name="(drawer)" options=\\{{ headerShown: false }} />
26041
+ <Stack.Screen
26042
+ name="modal"
26043
+ options=\\{{ title: "Modal", presentation: "modal" }}
26044
+ />
26045
+ </Stack>
26046
+ </GestureHandlerRootView>
26047
+ {{/unless}}
26048
+ {{/if}}
24335
26049
  {{/if}}
24336
26050
  );
24337
26051
  }
@@ -24556,7 +26270,11 @@ import { trpc } from "@/utils/trpc";
24556
26270
  import { Link } from "expo-router";
24557
26271
  import { Authenticated, AuthLoading, Unauthenticated, useQuery } from "convex/react";
24558
26272
  import { api } from "@{{ projectName }}/backend/convex/_generated/api";
24559
- import { useUser } from "@clerk/clerk-expo";
26273
+ import { useUser } from "@clerk/expo";
26274
+ import { SignOutButton } from "@/components/sign-out-button";
26275
+ {{else if (and (ne backend "convex") (eq auth "clerk"))}}
26276
+ import { Link } from "expo-router";
26277
+ import { useAuth, useUser } from "@clerk/expo";
24560
26278
  import { SignOutButton } from "@/components/sign-out-button";
24561
26279
  {{else if (and (eq backend "convex") (eq auth "better-auth"))}}
24562
26280
  import { useConvexAuth, useQuery } from "convex/react";
@@ -24580,6 +26298,9 @@ export default function Home() {
24580
26298
  const { user } = useUser();
24581
26299
  const healthCheck = useQuery(api.healthCheck.get);
24582
26300
  const privateData = useQuery(api.privateData.get);
26301
+ {{else if (and (ne backend "convex") (eq auth "clerk"))}}
26302
+ const { isLoaded, isSignedIn } = useAuth();
26303
+ const { user } = useUser();
24583
26304
  {{else if (and (eq backend "convex") (eq auth "better-auth"))}}
24584
26305
  const healthCheck = useQuery(api.healthCheck.get);
24585
26306
  const { isAuthenticated } = useConvexAuth();
@@ -24682,6 +26403,32 @@ export default function Home() {
24682
26403
  </AuthLoading>
24683
26404
  {{/if}}
24684
26405
 
26406
+ {{#if (and (ne backend "convex") (eq auth "clerk"))}}
26407
+ {!isLoaded ? (
26408
+ <Text style={styles.apiStatusText}>Loading...</Text>
26409
+ ) : isSignedIn ? (
26410
+ <View style={styles.userCard}>
26411
+ <View style={styles.userHeader}>
26412
+ <Text style={styles.userWelcome}>
26413
+ Welcome,{" "}
26414
+ <Text style={styles.userName}>{user?.fullName ?? user?.firstName ?? "there"}</Text>
26415
+ </Text>
26416
+ </View>
26417
+ <Text style={styles.userEmail}>{user?.emailAddresses[0]?.emailAddress}</Text>
26418
+ <SignOutButton />
26419
+ </View>
26420
+ ) : (
26421
+ <>
26422
+ <Link href="/(auth)/sign-in">
26423
+ <Text style={styles.apiStatusText}>Sign in</Text>
26424
+ </Link>
26425
+ <Link href="/(auth)/sign-up">
26426
+ <Text style={styles.apiStatusText}>Sign up</Text>
26427
+ </Link>
26428
+ </>
26429
+ )}
26430
+ {{/if}}
26431
+
24685
26432
  {{#if (and (eq backend "convex") (eq auth "better-auth"))}}
24686
26433
  {user ? (
24687
26434
  <View style={styles.userCard}>
@@ -24872,7 +26619,8 @@ const styles = StyleSheet.create((theme) => ({
24872
26619
  apiStatusText: {
24873
26620
  color: theme.colors.mutedForeground,
24874
26621
  },
24875
- }));`],
26622
+ }));
26623
+ `],
24876
26624
  ["frontend/native/unistyles/app/+html.tsx.hbs", `import { ScrollViewStyleReset } from "expo-router/html";
24877
26625
  import { type PropsWithChildren } from "react";
24878
26626
 
@@ -25349,8 +27097,17 @@ uniwind-types.d.ts
25349
27097
  ["frontend/native/uniwind/app/_layout.tsx.hbs", `{{#if (includes examples "ai")}}
25350
27098
  import "@/polyfills";
25351
27099
  {{/if}}
27100
+ {{#if (and (eq auth "clerk") (ne api "none") (ne backend "convex"))}}
27101
+ import { useEffect } from "react";
27102
+ import { setClerkAuthTokenGetter } from "@/utils/clerk-auth";
27103
+ {{/if}}
25352
27104
 
25353
27105
  import "@/global.css";
27106
+ {{#if (and (ne backend "convex") (eq auth "clerk"))}}
27107
+ import { ClerkProvider{{#unless (eq api "none")}}, useAuth{{/unless}} } from "@clerk/expo";
27108
+ import { tokenCache } from "@clerk/expo/token-cache";
27109
+ import { env } from "@{{projectName}}/env/native";
27110
+ {{/if}}
25354
27111
 
25355
27112
  {{#if (eq backend "convex")}}
25356
27113
  {{#if (eq auth "better-auth")}}
@@ -25364,9 +27121,9 @@ import "@/global.css";
25364
27121
  {{/if}}
25365
27122
 
25366
27123
  {{#if (eq auth "clerk")}}
25367
- import { ClerkProvider, useAuth } from "@clerk/clerk-expo";
27124
+ import { ClerkProvider, useAuth } from "@clerk/expo";
25368
27125
  import { ConvexProviderWithClerk } from "convex/react-clerk";
25369
- import { tokenCache } from "@clerk/clerk-expo/token-cache";
27126
+ import { tokenCache } from "@clerk/expo/token-cache";
25370
27127
  {{/if}}
25371
27128
  {{else}}
25372
27129
  {{#unless (eq api "none")}}
@@ -25397,6 +27154,22 @@ export const unstable_settings = {
25397
27154
  });
25398
27155
  {{/if}}
25399
27156
 
27157
+ {{#if (and (eq auth "clerk") (ne api "none") (ne backend "convex"))}}
27158
+ function ClerkApiAuthBridge() {
27159
+ const { getToken } = useAuth();
27160
+
27161
+ useEffect(() => {
27162
+ setClerkAuthTokenGetter(getToken);
27163
+
27164
+ return () => {
27165
+ setClerkAuthTokenGetter(null);
27166
+ };
27167
+ }, [getToken]);
27168
+
27169
+ return null;
27170
+ }
27171
+ {{/if}}
27172
+
25400
27173
  function StackLayout() {
25401
27174
  return (
25402
27175
  <Stack screenOptions=\\{{}}>
@@ -25449,35 +27222,65 @@ export default function Layout() {
25449
27222
  </AppThemeProvider>
25450
27223
  </KeyboardProvider>
25451
27224
  </GestureHandlerRootView>
25452
- </ConvexProvider>
27225
+ </ConvexProvider>
27226
+ {{/if}}
27227
+ {{else}}
27228
+ {{#if (eq auth "clerk")}}
27229
+ <ClerkProvider tokenCache={tokenCache} publishableKey={env.EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY}>
27230
+ {{#unless (eq api "none")}}
27231
+ <ClerkApiAuthBridge />
27232
+ <QueryClientProvider client={queryClient}>
27233
+ <GestureHandlerRootView style=\\{{ flex: 1 }}>
27234
+ <KeyboardProvider>
27235
+ <AppThemeProvider>
27236
+ <HeroUINativeProvider>
27237
+ <StackLayout />
27238
+ </HeroUINativeProvider>
27239
+ </AppThemeProvider>
27240
+ </KeyboardProvider>
27241
+ </GestureHandlerRootView>
27242
+ </QueryClientProvider>
27243
+ {{else}}
27244
+ <GestureHandlerRootView style=\\{{ flex: 1 }}>
27245
+ <KeyboardProvider>
27246
+ <AppThemeProvider>
27247
+ <HeroUINativeProvider>
27248
+ <StackLayout />
27249
+ </HeroUINativeProvider>
27250
+ </AppThemeProvider>
27251
+ </KeyboardProvider>
27252
+ </GestureHandlerRootView>
27253
+ {{/unless}}
27254
+ </ClerkProvider>
27255
+ {{else}}
27256
+ {{#unless (eq api "none")}}
27257
+ <QueryClientProvider client={queryClient}>
27258
+ <GestureHandlerRootView style=\\{{ flex: 1 }}>
27259
+ <KeyboardProvider>
27260
+ <AppThemeProvider>
27261
+ <HeroUINativeProvider>
27262
+ <StackLayout />
27263
+ </HeroUINativeProvider>
27264
+ </AppThemeProvider>
27265
+ </KeyboardProvider>
27266
+ </GestureHandlerRootView>
27267
+ </QueryClientProvider>
27268
+ {{else}}
27269
+ <GestureHandlerRootView style=\\{{ flex: 1 }}>
27270
+ <KeyboardProvider>
27271
+ <AppThemeProvider>
27272
+ <HeroUINativeProvider>
27273
+ <StackLayout />
27274
+ </HeroUINativeProvider>
27275
+ </AppThemeProvider>
27276
+ </KeyboardProvider>
27277
+ </GestureHandlerRootView>
27278
+ {{/unless}}
25453
27279
  {{/if}}
25454
- {{else}}
25455
- {{#unless (eq api "none")}}
25456
- <QueryClientProvider client={queryClient}>
25457
- <GestureHandlerRootView style=\\{{ flex: 1 }}>
25458
- <KeyboardProvider>
25459
- <AppThemeProvider>
25460
- <HeroUINativeProvider>
25461
- <StackLayout />
25462
- </HeroUINativeProvider>
25463
- </AppThemeProvider>
25464
- </KeyboardProvider>
25465
- </GestureHandlerRootView>
25466
- </QueryClientProvider>
25467
- {{else}}
25468
- <GestureHandlerRootView style=\\{{ flex: 1 }}>
25469
- <KeyboardProvider>
25470
- <AppThemeProvider>
25471
- <HeroUINativeProvider>
25472
- <StackLayout />
25473
- </HeroUINativeProvider>
25474
- </AppThemeProvider>
25475
- </KeyboardProvider>
25476
- </GestureHandlerRootView>
25477
- {{/unless}}
25478
27280
  {{/if}}
25479
27281
  );
25480
- }`],
27282
+ }
27283
+ `],
25481
27284
  ["frontend/native/uniwind/app/(drawer)/_layout.tsx.hbs", `import React, { useCallback } from "react";
25482
27285
  import { Ionicons, MaterialIcons } from "@expo/vector-icons";
25483
27286
  import { Link } from "expo-router";
@@ -25662,7 +27465,11 @@ import { trpc } from "@/utils/trpc";
25662
27465
  import { Link } from "expo-router";
25663
27466
  import { Authenticated, AuthLoading, Unauthenticated, useQuery } from "convex/react";
25664
27467
  import { api } from "@{{projectName}}/backend/convex/_generated/api";
25665
- import { useUser } from "@clerk/clerk-expo";
27468
+ import { useUser } from "@clerk/expo";
27469
+ import { SignOutButton } from "@/components/sign-out-button";
27470
+ {{else if (and (ne backend "convex") (eq auth "clerk"))}}
27471
+ import { Link } from "expo-router";
27472
+ import { useAuth, useUser } from "@clerk/expo";
25666
27473
  import { SignOutButton } from "@/components/sign-out-button";
25667
27474
  {{else if (and (eq backend "convex") (eq auth "better-auth"))}}
25668
27475
  import { useConvexAuth, useQuery } from "convex/react";
@@ -25690,6 +27497,9 @@ const healthCheck = useQuery(trpc.healthCheck.queryOptions());
25690
27497
  const { user } = useUser();
25691
27498
  const healthCheck = useQuery(api.healthCheck.get);
25692
27499
  const privateData = useQuery(api.privateData.get);
27500
+ {{else if (and (ne backend "convex") (eq auth "clerk"))}}
27501
+ const { isLoaded, isSignedIn } = useAuth();
27502
+ const { user } = useUser();
25693
27503
  {{else if (and (eq backend "convex") (eq auth "better-auth"))}}
25694
27504
  const healthCheck = useQuery(api.healthCheck.get);
25695
27505
  const { isAuthenticated } = useConvexAuth();
@@ -25796,6 +27606,37 @@ return (
25796
27606
  </AuthLoading>
25797
27607
  {{/if}}
25798
27608
 
27609
+ {{#if (and (ne backend "convex") (eq auth "clerk"))}}
27610
+ {!isLoaded ? (
27611
+ <View className="mt-4 items-center">
27612
+ <Spinner size="sm" />
27613
+ </View>
27614
+ ) : isSignedIn ? (
27615
+ <Surface variant="secondary" className="mt-5 p-4 rounded-xl">
27616
+ <View className="flex-row items-center justify-between">
27617
+ <View className="flex-1">
27618
+ <Text className="text-foreground font-medium">
27619
+ {user?.fullName ?? user?.firstName ?? "Welcome"}
27620
+ </Text>
27621
+ <Text className="text-muted text-xs mt-0.5">
27622
+ {user?.emailAddresses[0]?.emailAddress}
27623
+ </Text>
27624
+ </View>
27625
+ <SignOutButton />
27626
+ </View>
27627
+ </Surface>
27628
+ ) : (
27629
+ <View className="mt-4 gap-3">
27630
+ <Link href="/(auth)/sign-in" asChild>
27631
+ <Button variant="secondary"><Button.Label>Sign In</Button.Label></Button>
27632
+ </Link>
27633
+ <Link href="/(auth)/sign-up" asChild>
27634
+ <Button variant="ghost"><Button.Label>Sign Up</Button.Label></Button>
27635
+ </Link>
27636
+ </View>
27637
+ )}
27638
+ {{/if}}
27639
+
25799
27640
  {{#if (and (eq backend "convex") (eq auth "better-auth"))}}
25800
27641
  {user ? (
25801
27642
  <Surface variant="secondary" className="mb-4 p-4 rounded-xl">
@@ -26142,6 +27983,8 @@ module.exports = uniwindConfig;
26142
27983
  "**/*.tsx"
26143
27984
  ]
26144
27985
  }`],
27986
+ ["frontend/native/uniwind/uniwind-env.d.ts", `/// <reference types="uniwind/types" />
27987
+ `],
26145
27988
  ["frontend/nuxt/_gitignore", `# Nuxt dev/build outputs
26146
27989
  .output
26147
27990
  .data
@@ -26479,7 +28322,7 @@ initOpenNextCloudflareForDev();
26479
28322
  "dependencies": {
26480
28323
  "@{{projectName}}/ui": "{{#if (eq packageManager "npm")}}*{{else}}workspace:*{{/if}}",
26481
28324
  "lucide-react": "^0.546.0",
26482
- "next": "^16.1.1",
28325
+ "next": "^16.2.0",
26483
28326
  "next-themes": "^0.4.6",
26484
28327
  "react": "^19.2.3",
26485
28328
  "react-dom": "^19.2.3",
@@ -26506,8 +28349,8 @@ export default config;
26506
28349
  ["frontend/react/next/src/app/layout.tsx.hbs", `import type { Metadata } from "next";
26507
28350
  import { Geist, Geist_Mono } from "next/font/google";
26508
28351
  import "../index.css";
26509
- {{#if (eq auth "clerk")}}{{#if (eq backend "convex")}}import { ClerkProvider } from "@clerk/nextjs";
26510
- {{/if}}{{/if}}{{#if (and (eq backend "convex") (eq auth "better-auth"))}}
28352
+ {{#if (eq auth "clerk")}}import { ClerkProvider } from "@clerk/nextjs";
28353
+ {{/if}}{{#if (and (eq backend "convex") (eq auth "better-auth"))}}
26511
28354
  import { getToken } from "@/lib/auth-server";
26512
28355
  {{/if}}
26513
28356
  import Providers from "@/components/providers";
@@ -26556,12 +28399,12 @@ export default function RootLayout({
26556
28399
  }: Readonly<{
26557
28400
  children: React.ReactNode;
26558
28401
  }>) {
26559
- return (
28402
+ return (
26560
28403
  <html lang="en" suppressHydrationWarning>
26561
28404
  <body
26562
28405
  className={\`\${geistSans.variable} \${geistMono.variable} antialiased\`}
26563
28406
  >
26564
- {{#if (and (eq auth "clerk") (eq backend "convex"))}}<ClerkProvider>
28407
+ {{#if (eq auth "clerk")}}<ClerkProvider>
26565
28408
  <Providers>
26566
28409
  <div className="grid grid-rows-[auto_1fr] h-svh">
26567
28410
  <Header />
@@ -26700,9 +28543,15 @@ export function ModeToggle() {
26700
28543
  `],
26701
28544
  ["frontend/react/next/src/components/providers.tsx.hbs", `"use client";
26702
28545
 
28546
+ {{#if (and (eq auth "clerk") (ne api "none"))}}
28547
+ import { useEffect } from "react";
28548
+ import { setClerkAuthTokenGetter } from "@/utils/clerk-auth";
28549
+ {{/if}}
28550
+ {{#if (and (eq auth "clerk") (or (eq backend "convex") (ne api "none")))}}
28551
+ import { useAuth } from "@clerk/nextjs";
28552
+ {{/if}}
26703
28553
  {{#if (eq backend "convex")}}
26704
28554
  {{#if (eq auth "clerk")}}
26705
- import { useAuth } from "@clerk/nextjs";
26706
28555
  import { ConvexReactClient } from "convex/react";
26707
28556
  import { ConvexProviderWithClerk } from "convex/react-clerk";
26708
28557
  import { env } from "@{{projectName}}/env/web";
@@ -26734,6 +28583,22 @@ import { Toaster } from "@{{projectName}}/ui/components/sonner";
26734
28583
  const convex = new ConvexReactClient(env.NEXT_PUBLIC_CONVEX_URL);
26735
28584
  {{/if}}
26736
28585
 
28586
+ {{#if (and (eq auth "clerk") (ne backend "convex") (ne api "none"))}}
28587
+ function ClerkApiAuthBridge() {
28588
+ const { getToken } = useAuth();
28589
+
28590
+ useEffect(() => {
28591
+ setClerkAuthTokenGetter(getToken);
28592
+
28593
+ return () => {
28594
+ setClerkAuthTokenGetter(null);
28595
+ };
28596
+ }, [getToken]);
28597
+
28598
+ return null;
28599
+ }
28600
+ {{/if}}
28601
+
26737
28602
  export default function Providers({
26738
28603
  children,
26739
28604
  {{#if (and (eq backend "convex") (eq auth "better-auth"))}}
@@ -26771,6 +28636,9 @@ export default function Providers({
26771
28636
  {{else}}
26772
28637
  {{#unless (eq api "none")}}
26773
28638
  <QueryClientProvider client={queryClient}>
28639
+ {{#if (eq auth "clerk")}}
28640
+ <ClerkApiAuthBridge />
28641
+ {{/if}}
26774
28642
  {{#if (eq api "orpc")}}
26775
28643
  {children}
26776
28644
  {{/if}}
@@ -26815,7 +28683,7 @@ export function ThemeProvider({
26815
28683
  "resolveJsonModule": true,
26816
28684
  "isolatedModules": true,
26817
28685
  "verbatimModuleSyntax": true,
26818
- "jsx": "preserve",
28686
+ "jsx": "react-jsx",
26819
28687
  "incremental": true,
26820
28688
  "plugins": [
26821
28689
  {
@@ -26834,10 +28702,11 @@ export function ThemeProvider({
26834
28702
  {{#if (eq serverDeploy "cloudflare")}}
26835
28703
  "../server/env.d.ts",
26836
28704
  {{/if}}
26837
- "./next-env.d.ts",
26838
- "./**/*.ts",
26839
- "./**/*.tsx",
26840
- "./.next/types/**/*.ts"
28705
+ "next-env.d.ts",
28706
+ "**/*.ts",
28707
+ "**/*.tsx",
28708
+ ".next/types/**/*.ts",
28709
+ ".next/dev/types/**/*.ts"
26841
28710
  ],
26842
28711
  "exclude": [
26843
28712
  "./node_modules"
@@ -26887,6 +28756,9 @@ export function ThemeProvider({
26887
28756
  export default {
26888
28757
  ssr: false,
26889
28758
  appDirectory: "src",
28759
+ future: {
28760
+ v8_middleware: true,
28761
+ },
26890
28762
  } satisfies Config;
26891
28763
  `],
26892
28764
  ["frontend/react/react-router/src/components/mode-toggle.tsx.hbs", `import { Moon, Sun } from "lucide-react";
@@ -26944,16 +28816,23 @@ import "./index.css";
26944
28816
  import Header from "./components/header";
26945
28817
  import { ThemeProvider } from "./components/theme-provider";
26946
28818
  import { Toaster } from "@{{projectName}}/ui/components/sonner";
28819
+ {{#if (eq auth "clerk")}}
28820
+ import { ClerkProvider{{#if (or (eq backend "convex") (ne api "none"))}}, useAuth{{/if}} } from "@clerk/react-router";
28821
+ import { clerkMiddleware, rootAuthLoader } from "@clerk/react-router/server";
28822
+ {{/if}}
28823
+ {{#if (and (eq auth "clerk") (ne backend "convex") (ne api "none"))}}
28824
+ import { useEffect } from "react";
28825
+ import { setClerkAuthTokenGetter } from "@/utils/clerk-auth";
28826
+ {{/if}}
26947
28827
 
26948
28828
  {{#if (eq backend "convex")}}
26949
28829
  import { ConvexReactClient } from "convex/react";
26950
28830
  import { env } from "@{{projectName}}/env/web";
26951
- {{#if (eq auth "clerk")}}
26952
- import { ClerkProvider, useAuth } from "@clerk/clerk-react";
28831
+ {{#if (eq auth "clerk")}}
26953
28832
  import { ConvexProviderWithClerk } from "convex/react-clerk";
26954
- {{else}}
28833
+ {{else}}
26955
28834
  import { ConvexProvider } from "convex/react";
26956
- {{/if}}
28835
+ {{/if}}
26957
28836
  {{else}}
26958
28837
  {{#unless (eq api "none")}}
26959
28838
  import { QueryClientProvider } from "@tanstack/react-query";
@@ -26967,6 +28846,28 @@ import { queryClient } from "./utils/trpc";
26967
28846
  {{/unless}}
26968
28847
  {{/if}}
26969
28848
 
28849
+ {{#if (eq auth "clerk")}}
28850
+ export const middleware: Route.MiddlewareFunction[] = [clerkMiddleware()];
28851
+
28852
+ export const loader = (args: Route.LoaderArgs) => rootAuthLoader(args);
28853
+ {{/if}}
28854
+
28855
+ {{#if (and (eq auth "clerk") (ne backend "convex") (ne api "none"))}}
28856
+ function ClerkApiAuthBridge() {
28857
+ const { getToken } = useAuth();
28858
+
28859
+ useEffect(() => {
28860
+ setClerkAuthTokenGetter(getToken);
28861
+
28862
+ return () => {
28863
+ setClerkAuthTokenGetter(null);
28864
+ };
28865
+ }, [getToken]);
28866
+
28867
+ return null;
28868
+ }
28869
+ {{/if}}
28870
+
26970
28871
  export const links: Route.LinksFunction = () => [
26971
28872
  { rel: "preconnect", href: "https://fonts.googleapis.com" },
26972
28873
  { rel: "preconnect", href: "https://fonts.gstatic.com", crossOrigin: "anonymous" },
@@ -26996,11 +28897,15 @@ export function Layout({ children }: { children: React.ReactNode }) {
26996
28897
  }
26997
28898
 
26998
28899
  {{#if (eq backend "convex")}}
28900
+ {{#if (eq auth "clerk")}}
28901
+ export default function App({ loaderData }: Route.ComponentProps) {
28902
+ {{else}}
26999
28903
  export default function App() {
28904
+ {{/if}}
27000
28905
  const convex = new ConvexReactClient(env.VITE_CONVEX_URL);
27001
28906
  {{#if (eq auth "clerk")}}
27002
28907
  return (
27003
- <ClerkProvider publishableKey={env.VITE_CLERK_PUBLISHABLE_KEY}>
28908
+ <ClerkProvider loaderData={loaderData}>
27004
28909
  <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
27005
28910
  <ThemeProvider
27006
28911
  attribute="class"
@@ -27036,6 +28941,62 @@ export default function App() {
27036
28941
  );
27037
28942
  {{/if}}
27038
28943
  }
28944
+ {{else if (eq auth "clerk")}}
28945
+ export default function App({ loaderData }: Route.ComponentProps) {
28946
+ return (
28947
+ <ClerkProvider loaderData={loaderData}>
28948
+ {{#unless (eq api "none")}}
28949
+ <ClerkApiAuthBridge />
28950
+ {{/unless}}
28951
+ {{#if (eq api "orpc")}}
28952
+ <QueryClientProvider client={queryClient}>
28953
+ <ThemeProvider
28954
+ attribute="class"
28955
+ defaultTheme="dark"
28956
+ disableTransitionOnChange
28957
+ storageKey="vite-ui-theme"
28958
+ >
28959
+ <div className="grid grid-rows-[auto_1fr] h-svh">
28960
+ <Header />
28961
+ <Outlet />
28962
+ </div>
28963
+ <Toaster richColors />
28964
+ </ThemeProvider>
28965
+ <ReactQueryDevtools position="bottom" buttonPosition="bottom-right" />
28966
+ </QueryClientProvider>
28967
+ {{else if (eq api "trpc")}}
28968
+ <QueryClientProvider client={queryClient}>
28969
+ <ThemeProvider
28970
+ attribute="class"
28971
+ defaultTheme="dark"
28972
+ disableTransitionOnChange
28973
+ storageKey="vite-ui-theme"
28974
+ >
28975
+ <div className="grid grid-rows-[auto_1fr] h-svh">
28976
+ <Header />
28977
+ <Outlet />
28978
+ </div>
28979
+ <Toaster richColors />
28980
+ </ThemeProvider>
28981
+ <ReactQueryDevtools position="bottom" buttonPosition="bottom-right" />
28982
+ </QueryClientProvider>
28983
+ {{else}}
28984
+ <ThemeProvider
28985
+ attribute="class"
28986
+ defaultTheme="dark"
28987
+ disableTransitionOnChange
28988
+ storageKey="vite-ui-theme"
28989
+ >
28990
+ <div className="grid grid-rows-[auto_1fr] h-svh">
28991
+ <Header />
28992
+ <Outlet />
28993
+ </div>
28994
+ <Toaster richColors />
28995
+ </ThemeProvider>
28996
+ {{/if}}
28997
+ </ClerkProvider>
28998
+ );
28999
+ }
27039
29000
  {{else if (eq api "orpc")}}
27040
29001
  export default function App() {
27041
29002
  return (
@@ -27278,7 +29239,7 @@ export default defineConfig({
27278
29239
  "build": "vite build",
27279
29240
  "serve": "vite preview",
27280
29241
  "start": "vite",
27281
- "check-types": "tsc --noEmit"
29242
+ "check-types": "vite build && tsc --noEmit"
27282
29243
  },
27283
29244
  "dependencies": {
27284
29245
  "@hookform/resolvers": "^5.1.1",
@@ -27348,6 +29309,10 @@ export { useTheme } from "next-themes";
27348
29309
  `],
27349
29310
  ["frontend/react/tanstack-router/src/main.tsx.hbs", `import { RouterProvider, createRouter } from "@tanstack/react-router";
27350
29311
  import ReactDOM from "react-dom/client";
29312
+ {{#if (and (eq auth "clerk") (ne backend "convex") (ne api "none"))}}
29313
+ import { useEffect } from "react";
29314
+ import { setClerkAuthTokenGetter } from "@/utils/clerk-auth";
29315
+ {{/if}}
27351
29316
  import Loader from "./components/loader";
27352
29317
  import { routeTree } from "./routeTree.gen";
27353
29318
 
@@ -27359,11 +29324,15 @@ import { routeTree } from "./routeTree.gen";
27359
29324
  import { QueryClientProvider } from "@tanstack/react-query";
27360
29325
  import { queryClient, trpc } from "./utils/trpc";
27361
29326
  {{/if}}
29327
+ {{#if (or (eq backend "convex") (eq auth "clerk"))}}
29328
+ import { env } from "@{{projectName}}/env/web";
29329
+ {{/if}}
29330
+ {{#if (eq auth "clerk")}}
29331
+ import { ClerkProvider{{#if (or (eq backend "convex") (ne api "none"))}}, useAuth{{/if}} } from "@clerk/react";
29332
+ {{/if}}
27362
29333
  {{#if (eq backend "convex")}}
27363
29334
  import { ConvexReactClient } from "convex/react";
27364
- import { env } from "@{{projectName}}/env/web";
27365
29335
  {{#if (eq auth "clerk")}}
27366
- import { ClerkProvider, useAuth } from "@clerk/clerk-react";
27367
29336
  import { ConvexProviderWithClerk } from "convex/react-clerk";
27368
29337
  {{else if (eq auth "better-auth")}}
27369
29338
  import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react";
@@ -27374,6 +29343,22 @@ import { routeTree } from "./routeTree.gen";
27374
29343
  const convex = new ConvexReactClient(env.VITE_CONVEX_URL);
27375
29344
  {{/if}}
27376
29345
 
29346
+ {{#if (and (eq auth "clerk") (ne backend "convex") (ne api "none"))}}
29347
+ function ClerkApiAuthBridge() {
29348
+ const { getToken } = useAuth();
29349
+
29350
+ useEffect(() => {
29351
+ setClerkAuthTokenGetter(getToken);
29352
+
29353
+ return () => {
29354
+ setClerkAuthTokenGetter(null);
29355
+ };
29356
+ }, [getToken]);
29357
+
29358
+ return null;
29359
+ }
29360
+ {{/if}}
29361
+
27377
29362
  const router = createRouter({
27378
29363
  routeTree,
27379
29364
  defaultPreload: "intent",
@@ -27382,18 +29367,36 @@ const router = createRouter({
27382
29367
  context: { orpc, queryClient },
27383
29368
  Wrap: function WrapComponent({ children }: { children: React.ReactNode }) {
27384
29369
  return (
29370
+ {{#if (eq auth "clerk")}}
29371
+ <ClerkProvider publishableKey={env.VITE_CLERK_PUBLISHABLE_KEY}>
29372
+ <ClerkApiAuthBridge />
29373
+ <QueryClientProvider client={queryClient}>
29374
+ {children}
29375
+ </QueryClientProvider>
29376
+ </ClerkProvider>
29377
+ {{else}}
27385
29378
  <QueryClientProvider client={queryClient}>
27386
29379
  {children}
27387
29380
  </QueryClientProvider>
29381
+ {{/if}}
27388
29382
  );
27389
29383
  },
27390
29384
  {{else if (eq api "trpc")}}
27391
29385
  context: { trpc, queryClient },
27392
29386
  Wrap: function WrapComponent({ children }: { children: React.ReactNode }) {
27393
29387
  return (
29388
+ {{#if (eq auth "clerk")}}
29389
+ <ClerkProvider publishableKey={env.VITE_CLERK_PUBLISHABLE_KEY}>
29390
+ <ClerkApiAuthBridge />
29391
+ <QueryClientProvider client={queryClient}>
29392
+ {children}
29393
+ </QueryClientProvider>
29394
+ </ClerkProvider>
29395
+ {{else}}
27394
29396
  <QueryClientProvider client={queryClient}>
27395
29397
  {children}
27396
29398
  </QueryClientProvider>
29399
+ {{/if}}
27397
29400
  );
27398
29401
  },
27399
29402
  {{else if (eq backend "convex")}}
@@ -27415,6 +29418,11 @@ const router = createRouter({
27415
29418
  return <ConvexProvider client={convex}>{children}</ConvexProvider>;
27416
29419
  {{/if}}
27417
29420
  },
29421
+ {{else if (eq auth "clerk")}}
29422
+ context: {},
29423
+ Wrap: function WrapComponent({ children }: { children: React.ReactNode }) {
29424
+ return <ClerkProvider publishableKey={env.VITE_CLERK_PUBLISHABLE_KEY}>{children}</ClerkProvider>;
29425
+ },
27418
29426
  {{else}}
27419
29427
  context: {},
27420
29428
  {{/if}}
@@ -27734,6 +29742,9 @@ import { TRPCProvider } from "./utils/trpc";
27734
29742
  {{#unless (eq backend "self")}}
27735
29743
  import { env } from "@{{projectName}}/env/web";
27736
29744
  {{/unless}}
29745
+ {{#if (eq auth "clerk")}}
29746
+ import { getClerkAuthToken } from "@/utils/clerk-auth";
29747
+ {{/if}}
27737
29748
  {{else if (eq api "orpc")}}
27738
29749
  import { QueryClientProvider } from "@tanstack/react-query";
27739
29750
  import { orpc, queryClient } from "./utils/orpc";
@@ -27794,6 +29805,12 @@ const trpcClient = createTRPCClient<AppRouter>({
27794
29805
  links: [
27795
29806
  httpBatchLink({
27796
29807
  url: {{#if (eq backend "self")}}"/api/trpc"{{else}}\`\${env.VITE_SERVER_URL}/trpc\`{{/if}},
29808
+ {{#if (eq auth "clerk")}}
29809
+ headers: async () => {
29810
+ const token = await getClerkAuthToken();
29811
+ return token ? { Authorization: \`Bearer \${token}\` } : {};
29812
+ },
29813
+ {{/if}}
27797
29814
  {{#if (eq auth "better-auth")}}
27798
29815
  fetch(url, options) {
27799
29816
  return fetch(url, {
@@ -27880,8 +29897,14 @@ import type { QueryClient } from "@tanstack/react-query";
27880
29897
  {{/if}}
27881
29898
  {{/if}}
27882
29899
 
29900
+ {{#if (eq auth "clerk")}}
29901
+ import { ClerkProvider{{#if (or (eq backend "convex") (ne api "none"))}}, useAuth{{/if}} } from "@clerk/tanstack-react-start";
29902
+ {{/if}}
29903
+ {{#if (and (eq auth "clerk") (ne backend "convex") (ne api "none"))}}
29904
+ import { useEffect } from "react";
29905
+ import { setClerkAuthTokenGetter } from "@/utils/clerk-auth";
29906
+ {{/if}}
27883
29907
  {{#if (and (eq backend "convex") (eq auth "clerk"))}}
27884
- import { ClerkProvider, useAuth } from "@clerk/tanstack-react-start";
27885
29908
  import { auth } from "@clerk/tanstack-react-start/server";
27886
29909
  import { createServerFn } from "@tanstack/react-start";
27887
29910
  import { ConvexProviderWithClerk } from "convex/react-clerk";
@@ -27904,6 +29927,22 @@ const getAuth = createServerFn({ method: "GET" }).handler(async () => {
27904
29927
  import { ConvexProvider } from "convex/react";
27905
29928
  {{/if}}
27906
29929
 
29930
+ {{#if (and (eq auth "clerk") (ne backend "convex") (ne api "none"))}}
29931
+ function ClerkApiAuthBridge() {
29932
+ const { getToken } = useAuth();
29933
+
29934
+ useEffect(() => {
29935
+ setClerkAuthTokenGetter(getToken);
29936
+
29937
+ return () => {
29938
+ setClerkAuthTokenGetter(null);
29939
+ };
29940
+ }, [getToken]);
29941
+
29942
+ return null;
29943
+ }
29944
+ {{/if}}
29945
+
27907
29946
  {{#if (eq backend "convex")}}
27908
29947
  export interface RouterAppContext {
27909
29948
  queryClient: QueryClient;
@@ -28021,6 +30060,31 @@ function RootDocument() {
28021
30060
  </html>
28022
30061
  </ConvexBetterAuthProvider>
28023
30062
  );
30063
+ {{else if (eq auth "clerk")}}
30064
+ return (
30065
+ <ClerkProvider>
30066
+ {{#unless (eq api "none")}}
30067
+ <ClerkApiAuthBridge />
30068
+ {{/unless}}
30069
+ <html lang="en" className="dark">
30070
+ <head>
30071
+ <HeadContent />
30072
+ </head>
30073
+ <body>
30074
+ <div className="grid h-svh grid-rows-[auto_1fr]">
30075
+ <Header />
30076
+ <Outlet />
30077
+ </div>
30078
+ <Toaster richColors />
30079
+ <TanStackRouterDevtools position="bottom-left" />
30080
+ {{#unless (eq api "none")}}
30081
+ <ReactQueryDevtools position="bottom" buttonPosition="bottom-right" />
30082
+ {{/unless}}
30083
+ <Scripts />
30084
+ </body>
30085
+ </html>
30086
+ </ClerkProvider>
30087
+ );
28024
30088
  {{else if (eq backend "convex")}}
28025
30089
  const { convexQueryClient } = Route.useRouteContext();
28026
30090
  return (
@@ -29075,16 +31139,17 @@ export const env = createEnv({
29075
31139
  {{#if (eq auth "better-auth")}}
29076
31140
  EXPO_PUBLIC_CONVEX_SITE_URL: z.url(),
29077
31141
  {{/if}}
29078
- {{#if (eq auth "clerk")}}
29079
- EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),
29080
- {{/if}}
29081
31142
  {{else}}
29082
31143
  EXPO_PUBLIC_SERVER_URL: z.url(),
31144
+ {{/if}}
31145
+ {{#if (eq auth "clerk")}}
31146
+ EXPO_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),
29083
31147
  {{/if}}
29084
31148
  },
29085
31149
  runtimeEnv: process.env,
29086
31150
  emptyStringAsUndefined: true,
29087
- });`],
31151
+ });
31152
+ `],
29088
31153
  ["packages/env/src/server.ts.hbs", `{{#if (or (eq serverDeploy "cloudflare") (and (eq backend "self") (eq webDeploy "cloudflare")))}}
29089
31154
  /// <reference path="../env.d.ts" />
29090
31155
  // For Cloudflare Workers, env is accessed via cloudflare:workers module
@@ -29113,6 +31178,12 @@ export const env = createEnv({
29113
31178
  BETTER_AUTH_SECRET: z.string().min(32),
29114
31179
  BETTER_AUTH_URL: z.url(),
29115
31180
  {{/if}}
31181
+ {{#if (eq auth "clerk")}}
31182
+ CLERK_SECRET_KEY: z.string().min(1),
31183
+ {{#if (or (eq backend "express") (eq backend "fastify") (and (ne api "none") (or (eq backend "self") (eq backend "hono") (eq backend "elysia"))))}}
31184
+ CLERK_PUBLISHABLE_KEY: z.string().min(1),
31185
+ {{/if}}
31186
+ {{/if}}
29116
31187
  {{#if (eq payments "polar")}}
29117
31188
  POLAR_ACCESS_TOKEN: z.string().min(1),
29118
31189
  POLAR_SUCCESS_URL: z.url(),
@@ -29190,22 +31261,40 @@ export const env = createEnv({
29190
31261
  {{/if}}
29191
31262
  {{else if (eq backend "self")}}
29192
31263
  {{#if (includes frontend "next")}}
29193
- client: {},
29194
- runtimeEnv: {},
31264
+ client: {
31265
+ {{#if (eq auth "clerk")}}
31266
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),
31267
+ {{/if}}
31268
+ },
31269
+ runtimeEnv: {
31270
+ {{#if (eq auth "clerk")}}
31271
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
31272
+ {{/if}}
31273
+ },
29195
31274
  {{else if (includes frontend "nuxt")}}
29196
31275
  client: {},
29197
31276
  {{else}}
29198
31277
  clientPrefix: "VITE_",
29199
- client: {},
31278
+ client: {
31279
+ {{#if (eq auth "clerk")}}
31280
+ VITE_CLERK_PUBLISHABLE_KEY: z.string().min(1),
31281
+ {{/if}}
31282
+ },
29200
31283
  runtimeEnv: (import.meta as any).env,
29201
31284
  {{/if}}
29202
31285
  {{else if (ne backend "none")}}
29203
31286
  {{#if (includes frontend "next")}}
29204
31287
  client: {
29205
31288
  NEXT_PUBLIC_SERVER_URL: z.url(),
31289
+ {{#if (eq auth "clerk")}}
31290
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: z.string().min(1),
31291
+ {{/if}}
29206
31292
  },
29207
31293
  runtimeEnv: {
29208
31294
  NEXT_PUBLIC_SERVER_URL: process.env.NEXT_PUBLIC_SERVER_URL,
31295
+ {{#if (eq auth "clerk")}}
31296
+ NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY: process.env.NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY,
31297
+ {{/if}}
29209
31298
  },
29210
31299
  {{else if (includes frontend "nuxt")}}
29211
31300
  client: {
@@ -29221,12 +31310,16 @@ export const env = createEnv({
29221
31310
  clientPrefix: "VITE_",
29222
31311
  client: {
29223
31312
  VITE_SERVER_URL: z.url(),
31313
+ {{#if (eq auth "clerk")}}
31314
+ VITE_CLERK_PUBLISHABLE_KEY: z.string().min(1),
31315
+ {{/if}}
29224
31316
  },
29225
31317
  runtimeEnv: (import.meta as any).env,
29226
31318
  {{/if}}
29227
31319
  {{/if}}
29228
31320
  emptyStringAsUndefined: true,
29229
- });`],
31321
+ });
31322
+ `],
29230
31323
  ["packages/env/tsconfig.json.hbs", `{
29231
31324
  "extends": "@{{projectName}}/config/tsconfig.base.json",
29232
31325
  }
@@ -29310,6 +31403,9 @@ export const web = await Nextjs("web", {
29310
31403
  {{/if}}
29311
31404
  {{#if (eq auth "clerk")}}
29312
31405
  CLERK_SECRET_KEY: alchemy.secret.env.CLERK_SECRET_KEY!,
31406
+ {{#if (and (ne api "none") (or (eq backend "self") (eq backend "hono") (eq backend "elysia")))}}
31407
+ CLERK_PUBLISHABLE_KEY: alchemy.env.CLERK_PUBLISHABLE_KEY!,
31408
+ {{/if}}
29313
31409
  {{/if}}
29314
31410
  {{#if (and (includes examples "ai") (ne backend "convex"))}}
29315
31411
  GOOGLE_GENERATIVE_AI_API_KEY: alchemy.secret.env.GOOGLE_GENERATIVE_AI_API_KEY!,
@@ -29362,6 +31458,9 @@ export const web = await Nuxt("web", {
29362
31458
  {{/if}}
29363
31459
  {{#if (eq auth "clerk")}}
29364
31460
  CLERK_SECRET_KEY: alchemy.secret.env.CLERK_SECRET_KEY!,
31461
+ {{#if (and (ne api "none") (or (eq backend "self") (eq backend "hono") (eq backend "elysia")))}}
31462
+ CLERK_PUBLISHABLE_KEY: alchemy.env.CLERK_PUBLISHABLE_KEY!,
31463
+ {{/if}}
29365
31464
  {{/if}}
29366
31465
  {{#if (and (includes examples "ai") (ne backend "convex"))}}
29367
31466
  GOOGLE_GENERATIVE_AI_API_KEY: alchemy.secret.env.GOOGLE_GENERATIVE_AI_API_KEY!,
@@ -29423,6 +31522,9 @@ export const web = await TanStackStart("web", {
29423
31522
  {{/if}}
29424
31523
  {{#if (eq auth "clerk")}}
29425
31524
  CLERK_SECRET_KEY: alchemy.secret.env.CLERK_SECRET_KEY!,
31525
+ {{#if (and (ne api "none") (or (eq backend "self") (eq backend "hono") (eq backend "elysia")))}}
31526
+ CLERK_PUBLISHABLE_KEY: alchemy.env.CLERK_PUBLISHABLE_KEY!,
31527
+ {{/if}}
29426
31528
  {{/if}}
29427
31529
  {{#if (and (includes examples "ai") (ne backend "convex"))}}
29428
31530
  GOOGLE_GENERATIVE_AI_API_KEY: alchemy.secret.env.GOOGLE_GENERATIVE_AI_API_KEY!,
@@ -29548,6 +31650,9 @@ export const server = await Worker("server", {
29548
31650
  {{/if}}
29549
31651
  {{#if (eq auth "clerk")}}
29550
31652
  CLERK_SECRET_KEY: alchemy.secret.env.CLERK_SECRET_KEY!,
31653
+ {{#if (and (ne api "none") (or (eq backend "self") (eq backend "hono") (eq backend "elysia")))}}
31654
+ CLERK_PUBLISHABLE_KEY: alchemy.env.CLERK_PUBLISHABLE_KEY!,
31655
+ {{/if}}
29551
31656
  {{/if}}
29552
31657
  {{#if (includes examples "ai")}}
29553
31658
  GOOGLE_GENERATIVE_AI_API_KEY: alchemy.secret.env.GOOGLE_GENERATIVE_AI_API_KEY!,
@@ -30526,7 +32631,7 @@ function SuccessPage() {
30526
32631
  </div>
30527
32632
  `]
30528
32633
  ]);
30529
- const TEMPLATE_COUNT = 444;
32634
+ const TEMPLATE_COUNT = 457;
30530
32635
 
30531
32636
  //#endregion
30532
32637
  export { EMBEDDED_TEMPLATES, GeneratorError, Handlebars, TEMPLATE_COUNT, VirtualFileSystem, dependencyVersionMap, generate, generateReproducibleCommand, isBinaryFile, processAddonTemplates, processAddonsDeps, processFileContent, processTemplateString, transformFilename, writeBtsConfigToVfs };