@better-t-stack/template-generator 3.28.3 → 3.30.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.d.mts +21 -16
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +1729 -648
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -694,8 +694,8 @@ function renameDevScriptsForAlchemy(vfs, config) {
|
|
|
694
694
|
//#region src/utils/add-deps.ts
|
|
695
695
|
const dependencyVersionMap = {
|
|
696
696
|
typescript: "^6",
|
|
697
|
-
"better-auth": "1.6.
|
|
698
|
-
"@better-auth/expo": "1.6.
|
|
697
|
+
"better-auth": "1.6.11",
|
|
698
|
+
"@better-auth/expo": "1.6.11",
|
|
699
699
|
"@clerk/backend": "^3.2.1",
|
|
700
700
|
"@clerk/express": "^2.0.5",
|
|
701
701
|
"@clerk/fastify": "^3.1.3",
|
|
@@ -715,16 +715,17 @@ const dependencyVersionMap = {
|
|
|
715
715
|
"@types/ws": "^8.18.1",
|
|
716
716
|
ws: "^8.18.3",
|
|
717
717
|
mysql2: "^3.14.0",
|
|
718
|
-
"@prisma/client": "^7.
|
|
719
|
-
prisma: "^7.
|
|
720
|
-
"@prisma/adapter-d1": "^7.
|
|
721
|
-
"@prisma/adapter-neon": "^7.
|
|
722
|
-
"@prisma/adapter-mariadb": "^7.
|
|
723
|
-
"@prisma/adapter-libsql": "^7.
|
|
724
|
-
"@prisma/adapter-better-sqlite3": "^7.
|
|
725
|
-
"@prisma/adapter-pg": "^7.
|
|
726
|
-
"@prisma/adapter-planetscale": "^7.
|
|
727
|
-
mongoose: "^
|
|
718
|
+
"@prisma/client": "^7.8.0",
|
|
719
|
+
prisma: "^7.8.0",
|
|
720
|
+
"@prisma/adapter-d1": "^7.8.0",
|
|
721
|
+
"@prisma/adapter-neon": "^7.8.0",
|
|
722
|
+
"@prisma/adapter-mariadb": "^7.8.0",
|
|
723
|
+
"@prisma/adapter-libsql": "^7.8.0",
|
|
724
|
+
"@prisma/adapter-better-sqlite3": "^7.8.0",
|
|
725
|
+
"@prisma/adapter-pg": "^7.8.0",
|
|
726
|
+
"@prisma/adapter-planetscale": "^7.8.0",
|
|
727
|
+
mongoose: "^9.6.2",
|
|
728
|
+
mongodb: "^7.2.0",
|
|
728
729
|
"vite-plugin-pwa": "^1.2.0",
|
|
729
730
|
"@vite-pwa/assets-generator": "^1.0.2",
|
|
730
731
|
"@tauri-apps/cli": "^2.4.0",
|
|
@@ -774,10 +775,11 @@ const dependencyVersionMap = {
|
|
|
774
775
|
convex: "^1.33.1",
|
|
775
776
|
"@convex-dev/react-query": "^0.1.0",
|
|
776
777
|
"@convex-dev/agent": "^0.3.2",
|
|
778
|
+
"@convex-dev/polar": "^0.9.1",
|
|
777
779
|
"convex-svelte": "^0.0.12",
|
|
778
780
|
"convex-nuxt": "0.1.5",
|
|
779
781
|
"convex-vue": "^0.1.5",
|
|
780
|
-
"@convex-dev/better-auth": "^0.12.
|
|
782
|
+
"@convex-dev/better-auth": "^0.12.2",
|
|
781
783
|
"@tanstack/svelte-query": "^5.85.3",
|
|
782
784
|
"@tanstack/svelte-query-devtools": "^5.85.3",
|
|
783
785
|
"@tanstack/vue-query-devtools": "^6.1.5",
|
|
@@ -806,8 +808,11 @@ const dependencyVersionMap = {
|
|
|
806
808
|
"@t3-oss/env-core": "^0.13.1",
|
|
807
809
|
"@t3-oss/env-nextjs": "^0.13.1",
|
|
808
810
|
"@t3-oss/env-nuxt": "^0.13.1",
|
|
809
|
-
"@polar-sh/better-auth": "^1.8.
|
|
810
|
-
"@polar-sh/
|
|
811
|
+
"@polar-sh/better-auth": "^1.8.4",
|
|
812
|
+
"@polar-sh/checkout": "^0.2.1",
|
|
813
|
+
"@polar-sh/sdk": "^0.47.1",
|
|
814
|
+
"@stripe/react-stripe-js": "^4.0.2",
|
|
815
|
+
"@stripe/stripe-js": "^7.9.0",
|
|
811
816
|
evlog: "^2.14.1"
|
|
812
817
|
};
|
|
813
818
|
/**
|
|
@@ -1488,7 +1493,7 @@ function addConvexDeps(vfs, frontend, frontendType) {
|
|
|
1488
1493
|
}
|
|
1489
1494
|
//#endregion
|
|
1490
1495
|
//#region src/processors/auth-deps.ts
|
|
1491
|
-
const CONVEX_BETTER_AUTH_VERSION = "1.6.9";
|
|
1496
|
+
const CONVEX_BETTER_AUTH_VERSION = "~1.6.9";
|
|
1492
1497
|
function processAuthDeps(vfs, config) {
|
|
1493
1498
|
const { auth, backend } = config;
|
|
1494
1499
|
if (!auth || auth === "none") return;
|
|
@@ -1599,7 +1604,7 @@ function processConvexAuthDeps(vfs, config) {
|
|
|
1599
1604
|
}
|
|
1600
1605
|
}
|
|
1601
1606
|
function processStandardAuthDeps(vfs, config) {
|
|
1602
|
-
const { auth, backend, frontend } = config;
|
|
1607
|
+
const { auth, backend, frontend, orm } = config;
|
|
1603
1608
|
const authPath = "packages/auth/package.json";
|
|
1604
1609
|
const apiPath = "packages/api/package.json";
|
|
1605
1610
|
const webPath = "apps/web/package.json";
|
|
@@ -1696,10 +1701,12 @@ function processStandardAuthDeps(vfs, config) {
|
|
|
1696
1701
|
}
|
|
1697
1702
|
} else if (auth === "better-auth") {
|
|
1698
1703
|
if (authExists) {
|
|
1704
|
+
const authDependencies = ["better-auth"];
|
|
1705
|
+
if (orm === "mongoose") authDependencies.push("mongodb");
|
|
1699
1706
|
addPackageDependency({
|
|
1700
1707
|
vfs,
|
|
1701
1708
|
packagePath: authPath,
|
|
1702
|
-
dependencies:
|
|
1709
|
+
dependencies: authDependencies
|
|
1703
1710
|
});
|
|
1704
1711
|
if (hasNative) addPackageDependency({
|
|
1705
1712
|
vfs,
|
|
@@ -1871,13 +1878,13 @@ function processPrismaDeps(vfs, config, dbPkgPath, webPkgPath, webExists) {
|
|
|
1871
1878
|
addPackageDependency({
|
|
1872
1879
|
vfs,
|
|
1873
1880
|
packagePath: dbPkgPath,
|
|
1874
|
-
customDependencies: { "@prisma/client": "6.19.
|
|
1875
|
-
customDevDependencies: { prisma: "6.19.
|
|
1881
|
+
customDependencies: { "@prisma/client": "6.19.3" },
|
|
1882
|
+
customDevDependencies: { prisma: "6.19.3" }
|
|
1876
1883
|
});
|
|
1877
1884
|
if (webExists) addPackageDependency({
|
|
1878
1885
|
vfs,
|
|
1879
1886
|
packagePath: webPkgPath,
|
|
1880
|
-
customDependencies: { "@prisma/client": "6.19.
|
|
1887
|
+
customDependencies: { "@prisma/client": "6.19.3" }
|
|
1881
1888
|
});
|
|
1882
1889
|
return;
|
|
1883
1890
|
}
|
|
@@ -2207,7 +2214,9 @@ function buildNativeVars(frontend, backend, auth) {
|
|
|
2207
2214
|
});
|
|
2208
2215
|
return vars;
|
|
2209
2216
|
}
|
|
2210
|
-
function buildConvexBackendVars(frontend, auth, examples) {
|
|
2217
|
+
function buildConvexBackendVars(frontend, auth, payments, examples) {
|
|
2218
|
+
const hasReactRouter = frontend.includes("react-router");
|
|
2219
|
+
const hasTanStackRouter = frontend.includes("tanstack-router");
|
|
2211
2220
|
const hasNextJs = frontend.includes("next");
|
|
2212
2221
|
const hasNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
|
|
2213
2222
|
const hasWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("tanstack-start") || hasNextJs || frontend.includes("nuxt") || frontend.includes("solid") || frontend.includes("svelte") || frontend.includes("astro");
|
|
@@ -2220,6 +2229,12 @@ function buildConvexBackendVars(frontend, auth, examples) {
|
|
|
2220
2229
|
comment: "Google AI API key for AI agent"
|
|
2221
2230
|
});
|
|
2222
2231
|
if (auth === "better-auth") {
|
|
2232
|
+
if (hasReactRouter || hasTanStackRouter) vars.push({
|
|
2233
|
+
key: "CONVEX_SITE_URL",
|
|
2234
|
+
value: "",
|
|
2235
|
+
condition: true,
|
|
2236
|
+
comment: "Same as CONVEX_URL but ends in .site"
|
|
2237
|
+
});
|
|
2223
2238
|
if (hasNative) vars.push({
|
|
2224
2239
|
key: "EXPO_PUBLIC_CONVEX_SITE_URL",
|
|
2225
2240
|
value: "",
|
|
@@ -2244,9 +2259,26 @@ function buildConvexBackendVars(frontend, auth, examples) {
|
|
|
2244
2259
|
comment: "Web app URL for authentication (for Expo web support)"
|
|
2245
2260
|
});
|
|
2246
2261
|
}
|
|
2262
|
+
if (payments === "polar") vars.push({
|
|
2263
|
+
key: "POLAR_ORGANIZATION_TOKEN",
|
|
2264
|
+
value: "",
|
|
2265
|
+
condition: true,
|
|
2266
|
+
comment: "Polar organization token"
|
|
2267
|
+
}, {
|
|
2268
|
+
key: "POLAR_WEBHOOK_SECRET",
|
|
2269
|
+
value: "",
|
|
2270
|
+
condition: true,
|
|
2271
|
+
comment: "Polar webhook secret"
|
|
2272
|
+
}, {
|
|
2273
|
+
key: "POLAR_SERVER",
|
|
2274
|
+
value: "sandbox",
|
|
2275
|
+
condition: true,
|
|
2276
|
+
comment: "Polar environment: sandbox or production"
|
|
2277
|
+
});
|
|
2247
2278
|
return vars;
|
|
2248
2279
|
}
|
|
2249
|
-
function buildConvexCommentBlocks(frontend, auth, examples) {
|
|
2280
|
+
function buildConvexCommentBlocks(frontend, auth, payments, examples) {
|
|
2281
|
+
const needsConvexSiteUrl = frontend.includes("react-router") || frontend.includes("tanstack-router");
|
|
2250
2282
|
const hasNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
|
|
2251
2283
|
const hasWeb = frontend.includes("react-router") || frontend.includes("tanstack-router") || frontend.includes("tanstack-start") || frontend.includes("next") || frontend.includes("nuxt") || frontend.includes("solid") || frontend.includes("svelte") || frontend.includes("astro");
|
|
2252
2284
|
const defaultSiteUrl = hasNative && !hasWeb ? "http://localhost:8081" : frontend.includes("react-router") || frontend.includes("svelte") ? "http://localhost:5173" : frontend.includes("astro") ? "http://localhost:4321" : "http://localhost:3001";
|
|
@@ -2257,16 +2289,28 @@ function buildConvexCommentBlocks(frontend, auth, examples) {
|
|
|
2257
2289
|
`;
|
|
2258
2290
|
if (auth === "better-auth") commentBlocks += `# Set Convex environment variables
|
|
2259
2291
|
# npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
|
|
2260
|
-
${hasWeb || hasNative ? `# npx convex env set SITE_URL ${defaultSiteUrl}\n` : ""}`;
|
|
2292
|
+
${needsConvexSiteUrl ? "# npx convex env set CONVEX_SITE_URL https://<YOUR_CONVEX_SITE_URL>\n" : ""}${hasWeb || hasNative ? `# npx convex env set SITE_URL ${defaultSiteUrl}\n` : ""}`;
|
|
2293
|
+
if (payments === "polar") commentBlocks += `# Set Polar environment variables
|
|
2294
|
+
# npx convex env set POLAR_ORGANIZATION_TOKEN your_polar_token
|
|
2295
|
+
# npx convex env set POLAR_WEBHOOK_SECRET your_polar_webhook_secret
|
|
2296
|
+
# Optional: npx convex env set POLAR_SERVER production
|
|
2297
|
+
# Create a Polar webhook at https://<your-convex-site-url>/polar/events
|
|
2298
|
+
# Enable: product.created, product.updated, subscription.created, subscription.updated
|
|
2299
|
+
|
|
2300
|
+
`;
|
|
2261
2301
|
return commentBlocks;
|
|
2262
2302
|
}
|
|
2263
|
-
function buildServerVars(backend, frontend, auth, api, database, dbSetup, runtime, webDeploy, serverDeploy, payments, examples) {
|
|
2303
|
+
function buildServerVars(backend, frontend, projectName, auth, api, database, dbSetup, runtime, webDeploy, serverDeploy, payments, examples) {
|
|
2264
2304
|
const hasReactRouter = frontend.includes("react-router");
|
|
2265
2305
|
const hasSvelte = frontend.includes("svelte");
|
|
2266
2306
|
const hasAstro = frontend.includes("astro");
|
|
2307
|
+
const hasNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
|
|
2308
|
+
const hasWeb = hasReactRouter || hasSvelte || hasAstro || frontend.includes("tanstack-router") || frontend.includes("tanstack-start") || frontend.includes("next") || frontend.includes("nuxt") || frontend.includes("solid");
|
|
2267
2309
|
let corsOrigin = "http://localhost:3001";
|
|
2268
2310
|
if (hasAstro) corsOrigin = "http://localhost:4321";
|
|
2269
2311
|
else if (hasReactRouter || hasSvelte) corsOrigin = "http://localhost:5173";
|
|
2312
|
+
const betterAuthUrl = backend === "self" ? hasSvelte ? "http://localhost:5173" : hasAstro ? "http://localhost:4321" : "http://localhost:3001" : "http://localhost:3000";
|
|
2313
|
+
const polarSuccessUrl = hasNative && !hasWeb ? `${betterAuthUrl}/polar/success` : `${corsOrigin}/success?checkout_id={CHECKOUT_ID}`;
|
|
2270
2314
|
let databaseUrl = null;
|
|
2271
2315
|
if (database !== "none" && dbSetup === "none") switch (database) {
|
|
2272
2316
|
case "postgres":
|
|
@@ -2298,7 +2342,7 @@ function buildServerVars(backend, frontend, auth, api, database, dbSetup, runtim
|
|
|
2298
2342
|
},
|
|
2299
2343
|
{
|
|
2300
2344
|
key: "BETTER_AUTH_URL",
|
|
2301
|
-
value:
|
|
2345
|
+
value: betterAuthUrl,
|
|
2302
2346
|
condition: hasBetterAuth
|
|
2303
2347
|
},
|
|
2304
2348
|
{
|
|
@@ -2318,7 +2362,7 @@ function buildServerVars(backend, frontend, auth, api, database, dbSetup, runtim
|
|
|
2318
2362
|
},
|
|
2319
2363
|
{
|
|
2320
2364
|
key: "POLAR_SUCCESS_URL",
|
|
2321
|
-
value:
|
|
2365
|
+
value: polarSuccessUrl,
|
|
2322
2366
|
condition: payments === "polar"
|
|
2323
2367
|
},
|
|
2324
2368
|
{
|
|
@@ -2339,7 +2383,7 @@ function buildServerVars(backend, frontend, auth, api, database, dbSetup, runtim
|
|
|
2339
2383
|
];
|
|
2340
2384
|
}
|
|
2341
2385
|
function processEnvVariables(vfs, config) {
|
|
2342
|
-
const { backend, frontend, database, auth, api, examples, dbSetup, webDeploy, serverDeploy, runtime, payments } = config;
|
|
2386
|
+
const { backend, frontend, projectName, database, auth, api, examples, dbSetup, webDeploy, serverDeploy, runtime, payments } = config;
|
|
2343
2387
|
const hasReactRouter = frontend.includes("react-router");
|
|
2344
2388
|
const hasTanStackRouter = frontend.includes("tanstack-router");
|
|
2345
2389
|
const hasTanStackStart = frontend.includes("tanstack-start");
|
|
@@ -2360,13 +2404,13 @@ function processEnvVariables(vfs, config) {
|
|
|
2360
2404
|
const convexBackendDir = "packages/backend";
|
|
2361
2405
|
if (vfs.directoryExists(convexBackendDir)) {
|
|
2362
2406
|
const envLocalPath = `${convexBackendDir}/.env.local`;
|
|
2363
|
-
const commentBlocks = buildConvexCommentBlocks(frontend, auth, examples);
|
|
2407
|
+
const commentBlocks = buildConvexCommentBlocks(frontend, auth, payments, examples);
|
|
2364
2408
|
if (commentBlocks) {
|
|
2365
2409
|
let currentContent = "";
|
|
2366
2410
|
if (vfs.exists(envLocalPath)) currentContent = vfs.readFile(envLocalPath) || "";
|
|
2367
2411
|
vfs.writeFile(envLocalPath, commentBlocks + currentContent);
|
|
2368
2412
|
}
|
|
2369
|
-
const convexBackendVars = buildConvexBackendVars(frontend, auth, examples);
|
|
2413
|
+
const convexBackendVars = buildConvexBackendVars(frontend, auth, payments, examples);
|
|
2370
2414
|
if (convexBackendVars.length > 0) {
|
|
2371
2415
|
let existingContent = "";
|
|
2372
2416
|
if (vfs.exists(envLocalPath)) existingContent = vfs.readFile(envLocalPath) || "";
|
|
@@ -2376,7 +2420,7 @@ function processEnvVariables(vfs, config) {
|
|
|
2376
2420
|
}
|
|
2377
2421
|
return;
|
|
2378
2422
|
}
|
|
2379
|
-
const serverVars = buildServerVars(backend, frontend, auth, api, database, dbSetup, runtime, webDeploy, serverDeploy, payments, examples);
|
|
2423
|
+
const serverVars = buildServerVars(backend, frontend, projectName, auth, api, database, dbSetup, runtime, webDeploy, serverDeploy, payments, examples);
|
|
2380
2424
|
if (backend === "self") {
|
|
2381
2425
|
const webDir = "apps/web";
|
|
2382
2426
|
if (vfs.directoryExists(webDir)) writeEnvFile(vfs, `${webDir}/.env`, serverVars);
|
|
@@ -2597,11 +2641,37 @@ function getDeployTargets() {
|
|
|
2597
2641
|
//#endregion
|
|
2598
2642
|
//#region src/processors/payments-deps.ts
|
|
2599
2643
|
function processPaymentsDeps(vfs, config) {
|
|
2600
|
-
const { payments, frontend } = config;
|
|
2644
|
+
const { payments, frontend, backend } = config;
|
|
2601
2645
|
if (!payments || payments === "none") return;
|
|
2646
|
+
const backendPath = "packages/backend/package.json";
|
|
2602
2647
|
const authPath = "packages/auth/package.json";
|
|
2603
2648
|
const webPath = "apps/web/package.json";
|
|
2604
2649
|
if (payments === "polar") {
|
|
2650
|
+
if (backend === "convex") {
|
|
2651
|
+
if (vfs.exists(backendPath)) addPackageDependency({
|
|
2652
|
+
vfs,
|
|
2653
|
+
packagePath: backendPath,
|
|
2654
|
+
dependencies: ["@convex-dev/polar", "@polar-sh/sdk"]
|
|
2655
|
+
});
|
|
2656
|
+
if (vfs.exists(webPath)) {
|
|
2657
|
+
if (frontend.some((f) => [
|
|
2658
|
+
"react-router",
|
|
2659
|
+
"tanstack-router",
|
|
2660
|
+
"tanstack-start",
|
|
2661
|
+
"next"
|
|
2662
|
+
].includes(f))) addPackageDependency({
|
|
2663
|
+
vfs,
|
|
2664
|
+
packagePath: webPath,
|
|
2665
|
+
dependencies: [
|
|
2666
|
+
"@convex-dev/polar",
|
|
2667
|
+
"@polar-sh/checkout",
|
|
2668
|
+
"@stripe/react-stripe-js",
|
|
2669
|
+
"@stripe/stripe-js"
|
|
2670
|
+
]
|
|
2671
|
+
});
|
|
2672
|
+
}
|
|
2673
|
+
return;
|
|
2674
|
+
}
|
|
2605
2675
|
if (vfs.exists(authPath)) addPackageDependency({
|
|
2606
2676
|
vfs,
|
|
2607
2677
|
packagePath: authPath,
|
|
@@ -3275,7 +3345,6 @@ function getDeployTasks() {
|
|
|
3275
3345
|
}
|
|
3276
3346
|
//#endregion
|
|
3277
3347
|
//#region src/processors/workspace-deps.ts
|
|
3278
|
-
const NATIVE_TYPESCRIPT_VERSION = "~5.9.2";
|
|
3279
3348
|
function processWorkspaceDeps(vfs, config) {
|
|
3280
3349
|
const { projectName, packageManager, runtime, backend, database, auth, api, serverDeploy, webDeploy } = config;
|
|
3281
3350
|
const workspaceVersion = packageManager === "npm" ? "*" : "workspace:*";
|
|
@@ -3411,10 +3480,7 @@ function processWorkspaceDeps(vfs, config) {
|
|
|
3411
3480
|
packagePath: "apps/native/package.json",
|
|
3412
3481
|
dependencies: commonDeps,
|
|
3413
3482
|
customDependencies: nativeDeps,
|
|
3414
|
-
customDevDependencies:
|
|
3415
|
-
...configDep,
|
|
3416
|
-
typescript: NATIVE_TYPESCRIPT_VERSION
|
|
3417
|
-
}
|
|
3483
|
+
customDevDependencies: configDep
|
|
3418
3484
|
});
|
|
3419
3485
|
}
|
|
3420
3486
|
}
|
|
@@ -3445,11 +3511,6 @@ function processDependencies(vfs, config) {
|
|
|
3445
3511
|
}
|
|
3446
3512
|
//#endregion
|
|
3447
3513
|
//#region src/template-handlers/utils.ts
|
|
3448
|
-
function hasTemplatesWithPrefix(templates, prefix) {
|
|
3449
|
-
const normalizedPrefix = prefix.endsWith("/") ? prefix : `${prefix}/`;
|
|
3450
|
-
for (const path of templates.keys()) if (path.startsWith(normalizedPrefix)) return true;
|
|
3451
|
-
return false;
|
|
3452
|
-
}
|
|
3453
3514
|
function processSingleTemplate(vfs, templates, templatePath, destPath, config) {
|
|
3454
3515
|
const templateKey = templatePath.endsWith(".hbs") ? templatePath : `${templatePath}.hbs`;
|
|
3455
3516
|
const content = templates.get(templateKey);
|
|
@@ -3725,7 +3786,6 @@ async function processAuthTemplates(vfs, templates, config) {
|
|
|
3725
3786
|
//#region src/template-handlers/payments.ts
|
|
3726
3787
|
async function processPaymentsTemplates(vfs, templates, config) {
|
|
3727
3788
|
if (!config.payments || config.payments === "none") return;
|
|
3728
|
-
if (config.backend === "convex") return;
|
|
3729
3789
|
const hasReactWeb = config.frontend.some((f) => [
|
|
3730
3790
|
"tanstack-router",
|
|
3731
3791
|
"react-router",
|
|
@@ -3735,7 +3795,10 @@ async function processPaymentsTemplates(vfs, templates, config) {
|
|
|
3735
3795
|
const hasNuxtWeb = config.frontend.includes("nuxt");
|
|
3736
3796
|
const hasSvelteWeb = config.frontend.includes("svelte");
|
|
3737
3797
|
const hasSolidWeb = config.frontend.includes("solid");
|
|
3738
|
-
if (config.backend
|
|
3798
|
+
if (config.backend === "convex") {
|
|
3799
|
+
processTemplatesFromPrefix(vfs, templates, `payments/${config.payments}/convex/backend`, "packages/backend", config);
|
|
3800
|
+
return;
|
|
3801
|
+
} else if (config.backend !== "none") processTemplatesFromPrefix(vfs, templates, `payments/${config.payments}/server/base`, "packages/auth", config);
|
|
3739
3802
|
if (hasReactWeb) {
|
|
3740
3803
|
const reactFramework = config.frontend.find((f) => [
|
|
3741
3804
|
"tanstack-router",
|
|
@@ -3829,11 +3892,9 @@ async function processExtrasTemplates(vfs, templates, config) {
|
|
|
3829
3892
|
"native-unistyles"
|
|
3830
3893
|
].includes(f));
|
|
3831
3894
|
const hasNuxt = config.frontend.includes("nuxt");
|
|
3832
|
-
if (config.packageManager === "pnpm")
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
if (config.packageManager === "bun") processTemplatesFromPrefix(vfs, templates, "extras/bunfig.toml", "", config);
|
|
3836
|
-
if (config.packageManager === "pnpm" && (hasNative || hasNuxt)) processTemplatesFromPrefix(vfs, templates, "extras/_npmrc", "", config);
|
|
3895
|
+
if (config.packageManager === "pnpm") processSingleTemplate(vfs, templates, "extras/pnpm-workspace.yaml", "pnpm-workspace.yaml", config);
|
|
3896
|
+
if (config.packageManager === "bun") processSingleTemplate(vfs, templates, "extras/bunfig.toml", "bunfig.toml", config);
|
|
3897
|
+
if (config.packageManager === "pnpm" && (hasNative || hasNuxt)) processSingleTemplate(vfs, templates, "extras/_npmrc", ".npmrc", config);
|
|
3837
3898
|
if (config.serverDeploy === "cloudflare" || config.backend === "self" && config.webDeploy === "cloudflare") processSingleTemplate(vfs, templates, "extras/env.d.ts", "packages/env/env.d.ts", config);
|
|
3838
3899
|
}
|
|
3839
3900
|
//#endregion
|
|
@@ -4792,7 +4853,7 @@ import { auth } from "@{{projectName}}/auth";
|
|
|
4792
4853
|
{{/if}}
|
|
4793
4854
|
{{/if}}
|
|
4794
4855
|
|
|
4795
|
-
export async function createContext(req: NextRequest){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
4856
|
+
export async function createContext({{#if (eq auth "none")}}_req{{else}}req{{/if}}: NextRequest){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
4796
4857
|
{{#if (eq auth "better-auth")}}
|
|
4797
4858
|
const session = await {{#if (or (eq runtime "workers") (eq serverDeploy "cloudflare") (and (eq backend "self") (eq webDeploy "cloudflare")))}}createAuth(){{else}}auth{{/if}}.api.getSession({
|
|
4798
4859
|
headers: req.headers,
|
|
@@ -4824,7 +4885,7 @@ import { auth } from "@{{projectName}}/auth";
|
|
|
4824
4885
|
{{/if}}
|
|
4825
4886
|
{{/if}}
|
|
4826
4887
|
|
|
4827
|
-
export async function createContext({ req }: { req: Request }){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
4888
|
+
export async function createContext({{#if (eq auth "none")}}_options{{else}}{ req }{{/if}}: { req: Request }){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
4828
4889
|
{{#if (eq auth "better-auth")}}
|
|
4829
4890
|
const session = await {{#if (or (eq runtime "workers") (eq serverDeploy "cloudflare") (and (eq backend "self") (eq webDeploy "cloudflare")))}}createAuth(){{else}}auth{{/if}}.api.getSession({
|
|
4830
4891
|
headers: req.headers,
|
|
@@ -4860,7 +4921,7 @@ export type CreateContextOptions = {
|
|
|
4860
4921
|
headers: Headers;
|
|
4861
4922
|
};
|
|
4862
4923
|
|
|
4863
|
-
export async function createContext({ headers }: CreateContextOptions) {
|
|
4924
|
+
export async function createContext({{#if (eq auth "none")}}_options{{else}}{ headers }{{/if}}: CreateContextOptions) {
|
|
4864
4925
|
{{#if (eq auth "better-auth")}}
|
|
4865
4926
|
const session = await {{#if (or (eq runtime "workers") (eq serverDeploy "cloudflare") (and (eq backend "self") (eq webDeploy "cloudflare")))}}createAuth(){{else}}auth{{/if}}.api.getSession({ headers });
|
|
4866
4927
|
return {
|
|
@@ -4894,7 +4955,7 @@ export type CreateContextOptions = {
|
|
|
4894
4955
|
{{/if}}
|
|
4895
4956
|
};
|
|
4896
4957
|
|
|
4897
|
-
export async function createContext({ headers{{#if (eq webDeploy "cloudflare")}}, env{{/if}} }: CreateContextOptions) {
|
|
4958
|
+
export async function createContext({{#if (eq auth "none")}}{{#if (eq webDeploy "cloudflare")}}{ env: _env }{{else}}_options{{/if}}{{else}}{ headers{{#if (eq webDeploy "cloudflare")}}, env{{/if}} }{{/if}}: CreateContextOptions) {
|
|
4898
4959
|
{{#if (eq auth "better-auth")}}
|
|
4899
4960
|
const session = await {{#if (or (eq runtime "workers") (eq serverDeploy "cloudflare") (and (eq backend "self") (eq webDeploy "cloudflare")))}}createAuth({{#if (eq webDeploy "cloudflare")}}env{{/if}}){{else}}auth{{/if}}.api.getSession({ headers });
|
|
4900
4961
|
return {
|
|
@@ -4928,7 +4989,7 @@ export type CreateContextOptions = {
|
|
|
4928
4989
|
headers: Headers;
|
|
4929
4990
|
};
|
|
4930
4991
|
|
|
4931
|
-
export async function createContext({ headers }: CreateContextOptions) {
|
|
4992
|
+
export async function createContext({{#if (eq auth "none")}}_options{{else}}{ headers }{{/if}}: CreateContextOptions) {
|
|
4932
4993
|
{{#if (eq auth "better-auth")}}
|
|
4933
4994
|
const session = await {{#if (or (eq runtime "workers") (eq serverDeploy "cloudflare") (and (eq backend "self") (eq webDeploy "cloudflare")))}}createAuth(){{else}}auth{{/if}}.api.getSession({ headers });
|
|
4934
4995
|
return {
|
|
@@ -4957,7 +5018,7 @@ export type CreateContextOptions = {
|
|
|
4957
5018
|
context: HonoContext;
|
|
4958
5019
|
};
|
|
4959
5020
|
|
|
4960
|
-
export async function createContext({ context }: CreateContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
5021
|
+
export async function createContext({{#if (eq auth "none")}}_options{{else}}{ context }{{/if}}: CreateContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
4961
5022
|
{{#if (eq auth "better-auth")}}
|
|
4962
5023
|
const session = await {{#if (or (eq runtime "workers") (eq serverDeploy "cloudflare") (and (eq backend "self") (eq webDeploy "cloudflare")))}}createAuth(){{else}}auth{{/if}}.api.getSession({
|
|
4963
5024
|
headers: context.req.raw.headers,
|
|
@@ -4990,7 +5051,7 @@ export type CreateContextOptions = {
|
|
|
4990
5051
|
context: ElysiaContext;
|
|
4991
5052
|
};
|
|
4992
5053
|
|
|
4993
|
-
export async function createContext({ context }: CreateContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
5054
|
+
export async function createContext({{#if (eq auth "none")}}_options{{else}}{ context }{{/if}}: CreateContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
4994
5055
|
{{#if (eq auth "better-auth")}}
|
|
4995
5056
|
const session = await auth.api.getSession({
|
|
4996
5057
|
headers: context.request.headers,
|
|
@@ -5026,7 +5087,7 @@ interface CreateContextOptions {
|
|
|
5026
5087
|
req: Request;
|
|
5027
5088
|
}
|
|
5028
5089
|
|
|
5029
|
-
export async function createContext(opts: CreateContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
5090
|
+
export async function createContext({{#if (eq auth "none")}}_opts{{else}}opts{{/if}}: CreateContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
5030
5091
|
{{#if (eq auth "better-auth")}}
|
|
5031
5092
|
const session = await auth.api.getSession({
|
|
5032
5093
|
headers: fromNodeHeaders(opts.req.headers),
|
|
@@ -5076,6 +5137,7 @@ export async function createContext(req: {{#if (eq auth "clerk")}}Parameters<typ
|
|
|
5076
5137
|
session: null,
|
|
5077
5138
|
};
|
|
5078
5139
|
{{else}}
|
|
5140
|
+
void req;
|
|
5079
5141
|
return {
|
|
5080
5142
|
auth: null,
|
|
5081
5143
|
session: null,
|
|
@@ -5094,7 +5156,7 @@ export async function createContext() {
|
|
|
5094
5156
|
|
|
5095
5157
|
export type Context = Awaited<ReturnType<typeof createContext>>;
|
|
5096
5158
|
`],
|
|
5097
|
-
["api/orpc/server/src/index.ts.hbs", `import { ORPCError, os } from "@orpc/server";
|
|
5159
|
+
["api/orpc/server/src/index.ts.hbs", `import { {{#if (or (eq auth "better-auth") (eq auth "clerk"))}}ORPCError, {{/if}}os } from "@orpc/server";
|
|
5098
5160
|
import type { Context } from "./context";
|
|
5099
5161
|
|
|
5100
5162
|
export const o = os.$context<Context>();
|
|
@@ -5716,7 +5778,7 @@ import { auth } from "@{{projectName}}/auth";
|
|
|
5716
5778
|
{{/if}}
|
|
5717
5779
|
{{/if}}
|
|
5718
5780
|
|
|
5719
|
-
export async function createContext(req: NextRequest){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
5781
|
+
export async function createContext({{#if (eq auth "none")}}_req{{else}}req{{/if}}: NextRequest){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
5720
5782
|
{{#if (eq auth "better-auth")}}
|
|
5721
5783
|
const session = await {{#if (or (eq runtime "workers") (eq serverDeploy "cloudflare") (and (eq backend "self") (eq webDeploy "cloudflare")))}}createAuth(){{else}}auth{{/if}}.api.getSession({
|
|
5722
5784
|
headers: req.headers,
|
|
@@ -5748,7 +5810,7 @@ import { auth } from "@{{projectName}}/auth";
|
|
|
5748
5810
|
{{/if}}
|
|
5749
5811
|
{{/if}}
|
|
5750
5812
|
|
|
5751
|
-
export async function createContext({ req }: { req: Request }){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
5813
|
+
export async function createContext({{#if (eq auth "none")}}_options{{else}}{ req }{{/if}}: { req: Request }){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
5752
5814
|
{{#if (eq auth "better-auth")}}
|
|
5753
5815
|
const session = await {{#if (or (eq runtime "workers") (eq serverDeploy "cloudflare") (and (eq backend "self") (eq webDeploy "cloudflare")))}}createAuth(){{else}}auth{{/if}}.api.getSession({
|
|
5754
5816
|
headers: req.headers,
|
|
@@ -5785,7 +5847,7 @@ export type CreateContextOptions = {
|
|
|
5785
5847
|
context: HonoContext;
|
|
5786
5848
|
};
|
|
5787
5849
|
|
|
5788
|
-
export async function createContext({ context }: CreateContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
5850
|
+
export async function createContext({{#if (eq auth "none")}}_options{{else}}{ context }{{/if}}: CreateContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
5789
5851
|
{{#if (eq auth "better-auth")}}
|
|
5790
5852
|
const session = await {{#if (or (eq runtime "workers") (eq serverDeploy "cloudflare") (and (eq backend "self") (eq webDeploy "cloudflare")))}}createAuth(){{else}}auth{{/if}}.api.getSession({
|
|
5791
5853
|
headers: context.req.raw.headers,
|
|
@@ -5818,7 +5880,7 @@ export type CreateContextOptions = {
|
|
|
5818
5880
|
context: ElysiaContext;
|
|
5819
5881
|
};
|
|
5820
5882
|
|
|
5821
|
-
export async function createContext({ context }: CreateContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
5883
|
+
export async function createContext({{#if (eq auth "none")}}_options{{else}}{ context }{{/if}}: CreateContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
5822
5884
|
{{#if (eq auth "better-auth")}}
|
|
5823
5885
|
const session = await auth.api.getSession({
|
|
5824
5886
|
headers: context.request.headers,
|
|
@@ -5850,7 +5912,7 @@ import { auth } from "@{{projectName}}/auth";
|
|
|
5850
5912
|
import { getAuth } from "@clerk/express";
|
|
5851
5913
|
{{/if}}
|
|
5852
5914
|
|
|
5853
|
-
export async function createContext(opts: CreateExpressContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
5915
|
+
export async function createContext({{#if (eq auth "none")}}_opts{{else}}opts{{/if}}: CreateExpressContextOptions){{#if (eq auth "clerk")}}: Promise<ClerkRequestContext>{{/if}} {
|
|
5854
5916
|
{{#if (eq auth "better-auth")}}
|
|
5855
5917
|
const session = await auth.api.getSession({
|
|
5856
5918
|
headers: fromNodeHeaders(opts.req.headers),
|
|
@@ -5898,6 +5960,7 @@ export async function createContext({ req }: CreateFastifyContextOptions){{#if (
|
|
|
5898
5960
|
session: null,
|
|
5899
5961
|
};
|
|
5900
5962
|
{{else}}
|
|
5963
|
+
void req;
|
|
5901
5964
|
return {
|
|
5902
5965
|
auth: null,
|
|
5903
5966
|
session: null,
|
|
@@ -5916,7 +5979,7 @@ export async function createContext() {
|
|
|
5916
5979
|
|
|
5917
5980
|
export type Context = Awaited<ReturnType<typeof createContext>>;
|
|
5918
5981
|
`],
|
|
5919
|
-
["api/trpc/server/src/index.ts.hbs", `import { initTRPC, TRPCError } from "@trpc/server";
|
|
5982
|
+
["api/trpc/server/src/index.ts.hbs", `import { initTRPC{{#if (or (eq auth "better-auth") (eq auth "clerk"))}}, TRPCError{{/if}} } from "@trpc/server";
|
|
5920
5983
|
import type { Context } from "./context";
|
|
5921
5984
|
|
|
5922
5985
|
export const t = initTRPC.context<Context>().create();
|
|
@@ -6184,11 +6247,14 @@ export const authComponent = createClient<DataModel>(components.betterAuth);
|
|
|
6184
6247
|
|
|
6185
6248
|
function createAuth(ctx: GenericCtx<DataModel>) {
|
|
6186
6249
|
return betterAuth({
|
|
6250
|
+
{{#if (or (includes frontend "tanstack-router") (includes frontend "react-router"))}}
|
|
6251
|
+
baseURL: process.env.CONVEX_SITE_URL,
|
|
6252
|
+
{{/if}}
|
|
6187
6253
|
{{#if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
|
|
6188
6254
|
baseURL: siteUrl,
|
|
6189
6255
|
{{/if}}
|
|
6190
6256
|
{{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
|
|
6191
|
-
trustedOrigins: [siteUrl, nativeAppUrl,
|
|
6257
|
+
trustedOrigins: [siteUrl, nativeAppUrl, "exp://"],
|
|
6192
6258
|
{{else if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}}
|
|
6193
6259
|
trustedOrigins: [siteUrl],
|
|
6194
6260
|
{{else if (or (includes frontend "tanstack-start") (includes frontend "next"))}}
|
|
@@ -6225,21 +6291,56 @@ export const getCurrentUser = query({
|
|
|
6225
6291
|
`],
|
|
6226
6292
|
["auth/better-auth/convex/backend/convex/http.ts.hbs", `import { httpRouter } from "convex/server";
|
|
6227
6293
|
import { authComponent, createAuth } from "./auth";
|
|
6294
|
+
{{#if (and (eq payments "polar") (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles")))}}
|
|
6295
|
+
import { httpAction } from "./_generated/server";
|
|
6296
|
+
{{/if}}
|
|
6297
|
+
{{#if (eq payments "polar")}}
|
|
6298
|
+
import { polar } from "./polar";
|
|
6299
|
+
{{/if}}
|
|
6228
6300
|
|
|
6229
6301
|
const http = httpRouter();
|
|
6230
6302
|
|
|
6231
|
-
{{#if (
|
|
6232
|
-
|
|
6233
|
-
|
|
6234
|
-
|
|
6235
|
-
|
|
6303
|
+
{{#if (and (eq payments "polar") (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles")))}}
|
|
6304
|
+
const nativeAppUrl = process.env.NATIVE_APP_URL || "{{projectName}}://";
|
|
6305
|
+
const allowedNativeProtocols = new Set(["exp:", new URL(nativeAppUrl).protocol]);
|
|
6306
|
+
|
|
6307
|
+
http.route({
|
|
6308
|
+
path: "/polar/success",
|
|
6309
|
+
method: "GET",
|
|
6310
|
+
handler: httpAction(async (_ctx, request) => {
|
|
6311
|
+
const requestUrl = new URL(request.url);
|
|
6312
|
+
const returnUrl = requestUrl.searchParams.get("returnUrl") || nativeAppUrl;
|
|
6313
|
+
|
|
6314
|
+
let redirectUrl: URL;
|
|
6315
|
+
try {
|
|
6316
|
+
redirectUrl = new URL(returnUrl);
|
|
6317
|
+
} catch {
|
|
6318
|
+
return new Response("Invalid return URL", { status: 400 });
|
|
6319
|
+
}
|
|
6320
|
+
|
|
6321
|
+
if (!allowedNativeProtocols.has(redirectUrl.protocol)) {
|
|
6322
|
+
return new Response("Invalid return URL", { status: 400 });
|
|
6323
|
+
}
|
|
6324
|
+
|
|
6325
|
+
return new Response(null, {
|
|
6326
|
+
status: 302,
|
|
6327
|
+
headers: {
|
|
6328
|
+
Location: redirectUrl.toString(),
|
|
6329
|
+
},
|
|
6330
|
+
});
|
|
6331
|
+
}),
|
|
6236
6332
|
});
|
|
6237
|
-
|
|
6238
|
-
authComponent.registerRoutes(http, createAuth, { cors: true });
|
|
6333
|
+
|
|
6239
6334
|
{{/if}}
|
|
6335
|
+
{{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles") (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}}
|
|
6336
|
+
authComponent.registerRoutes(http, createAuth, { cors: true });
|
|
6240
6337
|
{{else}}
|
|
6241
6338
|
authComponent.registerRoutes(http, createAuth);
|
|
6242
6339
|
{{/if}}
|
|
6340
|
+
{{#if (eq payments "polar")}}
|
|
6341
|
+
|
|
6342
|
+
polar.registerRoutes(http);
|
|
6343
|
+
{{/if}}
|
|
6243
6344
|
|
|
6244
6345
|
export default http;
|
|
6245
6346
|
`],
|
|
@@ -7560,6 +7661,10 @@ export const { GET, POST } = handler;
|
|
|
7560
7661
|
import SignInForm from "@/components/sign-in-form";
|
|
7561
7662
|
import SignUpForm from "@/components/sign-up-form";
|
|
7562
7663
|
import UserMenu from "@/components/user-menu";
|
|
7664
|
+
{{#if (eq payments "polar")}}
|
|
7665
|
+
import { CheckoutLink, CustomerPortalLink } from "@convex-dev/polar/react";
|
|
7666
|
+
import { buttonVariants } from "@{{projectName}}/ui/components/button";
|
|
7667
|
+
{{/if}}
|
|
7563
7668
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
7564
7669
|
import {
|
|
7565
7670
|
Authenticated,
|
|
@@ -7569,18 +7674,58 @@ import {
|
|
|
7569
7674
|
} from "convex/react";
|
|
7570
7675
|
import { useState } from "react";
|
|
7571
7676
|
|
|
7677
|
+
function DashboardContent() {
|
|
7678
|
+
const privateData = useQuery(api.privateData.get);
|
|
7679
|
+
{{#if (eq payments "polar")}}
|
|
7680
|
+
const products = useQuery(api.polar.listAllProducts);
|
|
7681
|
+
const subscription = useQuery(api.polar.getCurrentSubscription);
|
|
7682
|
+
|
|
7683
|
+
const product = products?.find((product: { isRecurring?: boolean }) => product.isRecurring);
|
|
7684
|
+
const hasActiveSubscription = Boolean(subscription);
|
|
7685
|
+
{{/if}}
|
|
7686
|
+
|
|
7687
|
+
return (
|
|
7688
|
+
<div>
|
|
7689
|
+
<h1>Dashboard</h1>
|
|
7690
|
+
<p>privateData: {privateData?.message}</p>
|
|
7691
|
+
{{#if (eq payments "polar")}}
|
|
7692
|
+
<p>Plan: {hasActiveSubscription ? "Active" : "Free"}</p>
|
|
7693
|
+
{subscription === undefined ? (
|
|
7694
|
+
<p>Loading subscription options...</p>
|
|
7695
|
+
) : hasActiveSubscription ? (
|
|
7696
|
+
<CustomerPortalLink
|
|
7697
|
+
polarApi={api.polar}
|
|
7698
|
+
className={buttonVariants({ variant: "outline" })}
|
|
7699
|
+
>
|
|
7700
|
+
Manage Subscription
|
|
7701
|
+
</CustomerPortalLink>
|
|
7702
|
+
) : products === undefined ? (
|
|
7703
|
+
<p>Loading subscription options...</p>
|
|
7704
|
+
) : product ? (
|
|
7705
|
+
<CheckoutLink
|
|
7706
|
+
polarApi={api.polar}
|
|
7707
|
+
productIds={[product.id]}
|
|
7708
|
+
embed={false}
|
|
7709
|
+
className={buttonVariants({ variant: "default" })}
|
|
7710
|
+
>
|
|
7711
|
+
Upgrade
|
|
7712
|
+
</CheckoutLink>
|
|
7713
|
+
) : (
|
|
7714
|
+
<p>No recurring plans available.</p>
|
|
7715
|
+
)}
|
|
7716
|
+
{{/if}}
|
|
7717
|
+
<UserMenu />
|
|
7718
|
+
</div>
|
|
7719
|
+
);
|
|
7720
|
+
}
|
|
7721
|
+
|
|
7572
7722
|
export default function DashboardPage() {
|
|
7573
7723
|
const [showSignIn, setShowSignIn] = useState(false);
|
|
7574
|
-
const privateData = useQuery(api.privateData.get);
|
|
7575
7724
|
|
|
7576
7725
|
return (
|
|
7577
7726
|
<>
|
|
7578
7727
|
<Authenticated>
|
|
7579
|
-
<
|
|
7580
|
-
<h1>Dashboard</h1>
|
|
7581
|
-
<p>privateData: {privateData?.message}</p>
|
|
7582
|
-
<UserMenu />
|
|
7583
|
-
</div>
|
|
7728
|
+
<DashboardContent />
|
|
7584
7729
|
</Authenticated>
|
|
7585
7730
|
<Unauthenticated>
|
|
7586
7731
|
{showSignIn ? (
|
|
@@ -8300,12 +8445,16 @@ import { env } from "@{{projectName}}/env/web";
|
|
|
8300
8445
|
|
|
8301
8446
|
export const authClient = createAuthClient({
|
|
8302
8447
|
baseURL: env.VITE_CONVEX_SITE_URL,
|
|
8303
|
-
plugins: [
|
|
8448
|
+
plugins: [convexClient(), crossDomainClient()],
|
|
8304
8449
|
});
|
|
8305
8450
|
`],
|
|
8306
8451
|
["auth/better-auth/convex/web/react/react-router/src/routes/dashboard.tsx.hbs", `import SignInForm from "@/components/sign-in-form";
|
|
8307
8452
|
import SignUpForm from "@/components/sign-up-form";
|
|
8308
8453
|
import UserMenu from "@/components/user-menu";
|
|
8454
|
+
{{#if (eq payments "polar")}}
|
|
8455
|
+
import { CheckoutLink, CustomerPortalLink } from "@convex-dev/polar/react";
|
|
8456
|
+
import { buttonVariants } from "@{{projectName}}/ui/components/button";
|
|
8457
|
+
{{/if}}
|
|
8309
8458
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
8310
8459
|
import {
|
|
8311
8460
|
Authenticated,
|
|
@@ -8317,11 +8466,44 @@ import { useState } from "react";
|
|
|
8317
8466
|
|
|
8318
8467
|
function PrivateDashboardContent() {
|
|
8319
8468
|
const privateData = useQuery(api.privateData.get);
|
|
8469
|
+
{{#if (eq payments "polar")}}
|
|
8470
|
+
const products = useQuery(api.polar.listAllProducts);
|
|
8471
|
+
const subscription = useQuery(api.polar.getCurrentSubscription);
|
|
8472
|
+
|
|
8473
|
+
const product = products?.find((product: { isRecurring?: boolean }) => product.isRecurring);
|
|
8474
|
+
const hasActiveSubscription = Boolean(subscription);
|
|
8475
|
+
{{/if}}
|
|
8320
8476
|
|
|
8321
8477
|
return (
|
|
8322
8478
|
<div>
|
|
8323
8479
|
<h1>Dashboard</h1>
|
|
8324
8480
|
<p>privateData: {privateData?.message}</p>
|
|
8481
|
+
{{#if (eq payments "polar")}}
|
|
8482
|
+
<p>Plan: {hasActiveSubscription ? "Active" : "Free"}</p>
|
|
8483
|
+
{subscription === undefined ? (
|
|
8484
|
+
<p>Loading subscription options...</p>
|
|
8485
|
+
) : hasActiveSubscription ? (
|
|
8486
|
+
<CustomerPortalLink
|
|
8487
|
+
polarApi={api.polar}
|
|
8488
|
+
className={buttonVariants({ variant: "outline" })}
|
|
8489
|
+
>
|
|
8490
|
+
Manage Subscription
|
|
8491
|
+
</CustomerPortalLink>
|
|
8492
|
+
) : products === undefined ? (
|
|
8493
|
+
<p>Loading subscription options...</p>
|
|
8494
|
+
) : product ? (
|
|
8495
|
+
<CheckoutLink
|
|
8496
|
+
polarApi={api.polar}
|
|
8497
|
+
productIds={[product.id]}
|
|
8498
|
+
embed={false}
|
|
8499
|
+
className={buttonVariants({ variant: "default" })}
|
|
8500
|
+
>
|
|
8501
|
+
Upgrade
|
|
8502
|
+
</CheckoutLink>
|
|
8503
|
+
) : (
|
|
8504
|
+
<p>No recurring plans available.</p>
|
|
8505
|
+
)}
|
|
8506
|
+
{{/if}}
|
|
8325
8507
|
<UserMenu />
|
|
8326
8508
|
</div>
|
|
8327
8509
|
);
|
|
@@ -8704,12 +8886,16 @@ import { env } from "@{{projectName}}/env/web";
|
|
|
8704
8886
|
|
|
8705
8887
|
export const authClient = createAuthClient({
|
|
8706
8888
|
baseURL: env.VITE_CONVEX_SITE_URL,
|
|
8707
|
-
plugins: [
|
|
8889
|
+
plugins: [convexClient(), crossDomainClient()],
|
|
8708
8890
|
});
|
|
8709
8891
|
`],
|
|
8710
8892
|
["auth/better-auth/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs", `import SignInForm from "@/components/sign-in-form";
|
|
8711
8893
|
import SignUpForm from "@/components/sign-up-form";
|
|
8712
8894
|
import UserMenu from "@/components/user-menu";
|
|
8895
|
+
{{#if (eq payments "polar")}}
|
|
8896
|
+
import { CheckoutLink, CustomerPortalLink } from "@convex-dev/polar/react";
|
|
8897
|
+
import { buttonVariants } from "@{{projectName}}/ui/components/button";
|
|
8898
|
+
{{/if}}
|
|
8713
8899
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
8714
8900
|
import { createFileRoute } from "@tanstack/react-router";
|
|
8715
8901
|
import {
|
|
@@ -8726,11 +8912,44 @@ export const Route = createFileRoute("/dashboard")({
|
|
|
8726
8912
|
|
|
8727
8913
|
function PrivateDashboardContent() {
|
|
8728
8914
|
const privateData = useQuery(api.privateData.get);
|
|
8915
|
+
{{#if (eq payments "polar")}}
|
|
8916
|
+
const products = useQuery(api.polar.listAllProducts);
|
|
8917
|
+
const subscription = useQuery(api.polar.getCurrentSubscription);
|
|
8918
|
+
|
|
8919
|
+
const product = products?.find((product: { isRecurring?: boolean }) => product.isRecurring);
|
|
8920
|
+
const hasActiveSubscription = Boolean(subscription);
|
|
8921
|
+
{{/if}}
|
|
8729
8922
|
|
|
8730
8923
|
return (
|
|
8731
8924
|
<div>
|
|
8732
8925
|
<h1>Dashboard</h1>
|
|
8733
8926
|
<p>privateData: {privateData?.message}</p>
|
|
8927
|
+
{{#if (eq payments "polar")}}
|
|
8928
|
+
<p>Plan: {hasActiveSubscription ? "Active" : "Free"}</p>
|
|
8929
|
+
{subscription === undefined ? (
|
|
8930
|
+
<p>Loading subscription options...</p>
|
|
8931
|
+
) : hasActiveSubscription ? (
|
|
8932
|
+
<CustomerPortalLink
|
|
8933
|
+
polarApi={api.polar}
|
|
8934
|
+
className={buttonVariants({ variant: "outline" })}
|
|
8935
|
+
>
|
|
8936
|
+
Manage Subscription
|
|
8937
|
+
</CustomerPortalLink>
|
|
8938
|
+
) : products === undefined ? (
|
|
8939
|
+
<p>Loading subscription options...</p>
|
|
8940
|
+
) : product ? (
|
|
8941
|
+
<CheckoutLink
|
|
8942
|
+
polarApi={api.polar}
|
|
8943
|
+
productIds={[product.id]}
|
|
8944
|
+
embed={false}
|
|
8945
|
+
className={buttonVariants({ variant: "default" })}
|
|
8946
|
+
>
|
|
8947
|
+
Upgrade
|
|
8948
|
+
</CheckoutLink>
|
|
8949
|
+
) : (
|
|
8950
|
+
<p>No recurring plans available.</p>
|
|
8951
|
+
)}
|
|
8952
|
+
{{/if}}
|
|
8734
8953
|
<UserMenu />
|
|
8735
8954
|
</div>
|
|
8736
8955
|
);
|
|
@@ -9134,6 +9353,10 @@ export const Route = createFileRoute("/api/auth/$")({
|
|
|
9134
9353
|
["auth/better-auth/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs", `import SignInForm from "@/components/sign-in-form";
|
|
9135
9354
|
import SignUpForm from "@/components/sign-up-form";
|
|
9136
9355
|
import UserMenu from "@/components/user-menu";
|
|
9356
|
+
{{#if (eq payments "polar")}}
|
|
9357
|
+
import { CheckoutLink, CustomerPortalLink } from "@convex-dev/polar/react";
|
|
9358
|
+
import { buttonVariants } from "@{{projectName}}/ui/components/button";
|
|
9359
|
+
{{/if}}
|
|
9137
9360
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
9138
9361
|
import { createFileRoute } from "@tanstack/react-router";
|
|
9139
9362
|
import {
|
|
@@ -9148,18 +9371,58 @@ export const Route = createFileRoute("/dashboard")({
|
|
|
9148
9371
|
component: RouteComponent,
|
|
9149
9372
|
});
|
|
9150
9373
|
|
|
9374
|
+
function PrivateDashboardContent() {
|
|
9375
|
+
const privateData = useQuery(api.privateData.get);
|
|
9376
|
+
{{#if (eq payments "polar")}}
|
|
9377
|
+
const products = useQuery(api.polar.listAllProducts);
|
|
9378
|
+
const subscription = useQuery(api.polar.getCurrentSubscription);
|
|
9379
|
+
|
|
9380
|
+
const product = products?.find((product: { isRecurring?: boolean }) => product.isRecurring);
|
|
9381
|
+
const hasActiveSubscription = Boolean(subscription);
|
|
9382
|
+
{{/if}}
|
|
9383
|
+
|
|
9384
|
+
return (
|
|
9385
|
+
<div>
|
|
9386
|
+
<h1>Dashboard</h1>
|
|
9387
|
+
<p>privateData: {privateData?.message}</p>
|
|
9388
|
+
{{#if (eq payments "polar")}}
|
|
9389
|
+
<p>Plan: {hasActiveSubscription ? "Active" : "Free"}</p>
|
|
9390
|
+
{subscription === undefined ? (
|
|
9391
|
+
<p>Loading subscription options...</p>
|
|
9392
|
+
) : hasActiveSubscription ? (
|
|
9393
|
+
<CustomerPortalLink
|
|
9394
|
+
polarApi={api.polar}
|
|
9395
|
+
className={buttonVariants({ variant: "outline" })}
|
|
9396
|
+
>
|
|
9397
|
+
Manage Subscription
|
|
9398
|
+
</CustomerPortalLink>
|
|
9399
|
+
) : products === undefined ? (
|
|
9400
|
+
<p>Loading subscription options...</p>
|
|
9401
|
+
) : product ? (
|
|
9402
|
+
<CheckoutLink
|
|
9403
|
+
polarApi={api.polar}
|
|
9404
|
+
productIds={[product.id]}
|
|
9405
|
+
embed={false}
|
|
9406
|
+
className={buttonVariants({ variant: "default" })}
|
|
9407
|
+
>
|
|
9408
|
+
Upgrade
|
|
9409
|
+
</CheckoutLink>
|
|
9410
|
+
) : (
|
|
9411
|
+
<p>No recurring plans available.</p>
|
|
9412
|
+
)}
|
|
9413
|
+
{{/if}}
|
|
9414
|
+
<UserMenu />
|
|
9415
|
+
</div>
|
|
9416
|
+
);
|
|
9417
|
+
}
|
|
9418
|
+
|
|
9151
9419
|
function RouteComponent() {
|
|
9152
9420
|
const [showSignIn, setShowSignIn] = useState(false);
|
|
9153
|
-
const privateData = useQuery(api.privateData.get);
|
|
9154
9421
|
|
|
9155
9422
|
return (
|
|
9156
9423
|
<>
|
|
9157
9424
|
<Authenticated>
|
|
9158
|
-
<
|
|
9159
|
-
<h1>Dashboard</h1>
|
|
9160
|
-
<p>privateData: {privateData?.message}</p>
|
|
9161
|
-
<UserMenu />
|
|
9162
|
-
</div>
|
|
9425
|
+
<PrivateDashboardContent />
|
|
9163
9426
|
</Authenticated>
|
|
9164
9427
|
<Unauthenticated>
|
|
9165
9428
|
{showSignIn ? (
|
|
@@ -9321,11 +9584,17 @@ export const Route = createFileRoute('/api/auth/$')({
|
|
|
9321
9584
|
},
|
|
9322
9585
|
})
|
|
9323
9586
|
`],
|
|
9324
|
-
["auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs", `import {
|
|
9587
|
+
["auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs", `import { Button, Column, Host, Text as ExpoUIText } from "@expo/ui";
|
|
9588
|
+
import { View, ScrollView, StyleSheet{{#if (eq payments "polar")}}, Alert{{/if}} } from "react-native";
|
|
9589
|
+
{{#if (eq payments "polar")}}
|
|
9590
|
+
import * as Linking from "expo-linking";
|
|
9591
|
+
import * as WebBrowser from "expo-web-browser";
|
|
9592
|
+
import { env } from "@{{projectName}}/env/native";
|
|
9593
|
+
{{/if}}
|
|
9325
9594
|
import { Container } from "@/components/container";
|
|
9326
9595
|
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
9327
9596
|
import { NAV_THEME } from "@/lib/constants";
|
|
9328
|
-
import { authClient } from "@/lib/auth-client";
|
|
9597
|
+
import { authClient{{#if (eq payments "polar")}}, polarNativeClient{{/if}} } from "@/lib/auth-client";
|
|
9329
9598
|
import { SignIn } from "@/components/sign-in";
|
|
9330
9599
|
import { SignUp } from "@/components/sign-up";
|
|
9331
9600
|
{{#if (eq api "orpc")}}
|
|
@@ -9353,70 +9622,158 @@ const isConnected = healthCheck?.data === "OK";
|
|
|
9353
9622
|
const isLoading = healthCheck?.isLoading;
|
|
9354
9623
|
{{/if}}
|
|
9355
9624
|
const { data: session } = authClient.useSession();
|
|
9625
|
+
{{#if (eq payments "polar")}}
|
|
9626
|
+
|
|
9627
|
+
const openPolarLink = async (url: string, returnUrl: string) => {
|
|
9628
|
+
await WebBrowser.openAuthSessionAsync(url, returnUrl);
|
|
9629
|
+
};
|
|
9630
|
+
|
|
9631
|
+
const getPolarReturnUrl = (returnUrl: string) => {
|
|
9632
|
+
const url = new URL("/polar/success", env.EXPO_PUBLIC_SERVER_URL);
|
|
9633
|
+
url.searchParams.set("returnUrl", returnUrl);
|
|
9634
|
+
return url.toString();
|
|
9635
|
+
};
|
|
9636
|
+
|
|
9637
|
+
const handlePolarCheckout = async () => {
|
|
9638
|
+
const returnUrl = Linking.createURL("/");
|
|
9639
|
+
const polarReturnUrl = getPolarReturnUrl(returnUrl);
|
|
9640
|
+
const { data, error } = await polarNativeClient.checkout({
|
|
9641
|
+
slug: "pro",
|
|
9642
|
+
redirect: false,
|
|
9643
|
+
successUrl: polarReturnUrl,
|
|
9644
|
+
returnUrl: polarReturnUrl,
|
|
9645
|
+
});
|
|
9646
|
+
|
|
9647
|
+
if (error || !data?.url) {
|
|
9648
|
+
Alert.alert("Checkout unavailable", error?.message ?? "Unable to create a checkout session.");
|
|
9649
|
+
return;
|
|
9650
|
+
}
|
|
9651
|
+
|
|
9652
|
+
await openPolarLink(data.url, returnUrl);
|
|
9653
|
+
};
|
|
9654
|
+
|
|
9655
|
+
const handlePolarPortal = async () => {
|
|
9656
|
+
const returnUrl = Linking.createURL("/");
|
|
9657
|
+
const { data, error } = await polarNativeClient.customer.portal({ redirect: false });
|
|
9658
|
+
|
|
9659
|
+
if (error || !data?.url) {
|
|
9660
|
+
Alert.alert("Portal unavailable", error?.message ?? "Unable to open the customer portal.");
|
|
9661
|
+
return;
|
|
9662
|
+
}
|
|
9663
|
+
|
|
9664
|
+
await openPolarLink(data.url, returnUrl);
|
|
9665
|
+
};
|
|
9666
|
+
{{/if}}
|
|
9356
9667
|
|
|
9357
9668
|
return (
|
|
9358
9669
|
<Container>
|
|
9359
|
-
<ScrollView style={styles.scrollView}>
|
|
9670
|
+
<ScrollView style={styles.scrollView} contentInsetAdjustmentBehavior="never">
|
|
9360
9671
|
<View style={styles.content}>
|
|
9361
|
-
<
|
|
9362
|
-
|
|
9363
|
-
|
|
9672
|
+
<Host style={styles.titleHost}>
|
|
9673
|
+
<ExpoUIText
|
|
9674
|
+
textStyle=\\{{ color: theme.text, fontSize: 24, fontWeight: "bold", textAlign: "center" }}
|
|
9675
|
+
>
|
|
9676
|
+
BETTER T STACK
|
|
9677
|
+
</ExpoUIText>
|
|
9678
|
+
</Host>
|
|
9364
9679
|
|
|
9365
9680
|
{session?.user ? (
|
|
9366
9681
|
<View style={[styles.userCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
9367
|
-
<
|
|
9368
|
-
<
|
|
9369
|
-
|
|
9370
|
-
|
|
9371
|
-
|
|
9372
|
-
|
|
9373
|
-
|
|
9374
|
-
|
|
9375
|
-
|
|
9376
|
-
|
|
9377
|
-
|
|
9378
|
-
|
|
9379
|
-
|
|
9380
|
-
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9682
|
+
<Host style={styles.userHeader} matchContents=\\{{ vertical: true }}>
|
|
9683
|
+
<Column spacing={8}>
|
|
9684
|
+
<ExpoUIText textStyle=\\{{ color: theme.text, fontSize: 16 }}>
|
|
9685
|
+
{\`Welcome, \${session.user.name}\`}
|
|
9686
|
+
</ExpoUIText>
|
|
9687
|
+
<ExpoUIText
|
|
9688
|
+
textStyle=\\{{ color: theme.text, fontSize: 14 }}
|
|
9689
|
+
style=\\{{ opacity: 0.7 }}
|
|
9690
|
+
>
|
|
9691
|
+
{session.user.email}
|
|
9692
|
+
</ExpoUIText>
|
|
9693
|
+
</Column>
|
|
9694
|
+
</Host>
|
|
9695
|
+
<Host matchContents=\\{{ vertical: true }}>
|
|
9696
|
+
<Button
|
|
9697
|
+
label="Sign Out"
|
|
9698
|
+
variant="outlined"
|
|
9699
|
+
onPress={() => {
|
|
9700
|
+
authClient.signOut();
|
|
9701
|
+
{{#if (eq api "orpc")}}
|
|
9702
|
+
queryClient.invalidateQueries();
|
|
9703
|
+
{{/if}}
|
|
9704
|
+
{{#if (eq api "trpc")}}
|
|
9705
|
+
queryClient.invalidateQueries();
|
|
9706
|
+
{{/if}}
|
|
9707
|
+
}}
|
|
9708
|
+
/>
|
|
9709
|
+
</Host>
|
|
9710
|
+
{{#if (eq payments "polar")}}
|
|
9711
|
+
<Host style={styles.paymentActions} matchContents=\\{{ vertical: true }}>
|
|
9712
|
+
<Column spacing={8}>
|
|
9713
|
+
<Button label="Upgrade to Pro" onPress={handlePolarCheckout} />
|
|
9714
|
+
<Button
|
|
9715
|
+
label="Manage Subscription"
|
|
9716
|
+
variant="outlined"
|
|
9717
|
+
onPress={handlePolarPortal}
|
|
9718
|
+
/>
|
|
9719
|
+
</Column>
|
|
9720
|
+
</Host>
|
|
9721
|
+
{{/if}}
|
|
9387
9722
|
</View>
|
|
9388
9723
|
) : null}
|
|
9389
9724
|
|
|
9390
9725
|
{{#unless (eq api "none")}}
|
|
9391
9726
|
<View style={[styles.statusCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
9392
|
-
<
|
|
9393
|
-
|
|
9394
|
-
|
|
9727
|
+
<Host style={styles.cardTitleHost} matchContents=\\{{ vertical: true }}>
|
|
9728
|
+
<ExpoUIText
|
|
9729
|
+
textStyle=\\{{ color: theme.text, fontSize: 16, fontWeight: "bold" }}
|
|
9730
|
+
>
|
|
9731
|
+
System Status
|
|
9732
|
+
</ExpoUIText>
|
|
9733
|
+
</Host>
|
|
9395
9734
|
<View style={styles.statusRow}>
|
|
9396
9735
|
<View style={[styles.statusIndicator, { backgroundColor: isConnected ? "#10b981" : "#ef4444" }]} />
|
|
9397
9736
|
<View style={styles.statusContent}>
|
|
9398
|
-
<
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
9405
|
-
|
|
9406
|
-
|
|
9407
|
-
|
|
9737
|
+
<Host matchContents=\\{{ vertical: true }}>
|
|
9738
|
+
<Column spacing={4}>
|
|
9739
|
+
<ExpoUIText
|
|
9740
|
+
textStyle=\\{{ color: theme.text, fontSize: 14, fontWeight: "bold" }}
|
|
9741
|
+
>
|
|
9742
|
+
{{#if (eq api "orpc")}}ORPC{{else}}TRPC{{/if}} Backend
|
|
9743
|
+
</ExpoUIText>
|
|
9744
|
+
<ExpoUIText
|
|
9745
|
+
textStyle=\\{{ color: theme.text, fontSize: 12 }}
|
|
9746
|
+
style=\\{{ opacity: 0.7 }}
|
|
9747
|
+
>
|
|
9748
|
+
{isLoading
|
|
9749
|
+
? "Checking connection..."
|
|
9750
|
+
: isConnected
|
|
9751
|
+
? "Connected to API"
|
|
9752
|
+
: "API Disconnected"}
|
|
9753
|
+
</ExpoUIText>
|
|
9754
|
+
</Column>
|
|
9755
|
+
</Host>
|
|
9408
9756
|
</View>
|
|
9409
9757
|
</View>
|
|
9410
9758
|
</View>
|
|
9411
9759
|
|
|
9412
9760
|
<View style={[styles.privateDataCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
9413
|
-
<
|
|
9414
|
-
|
|
9415
|
-
|
|
9761
|
+
<Host style={styles.cardTitleHost} matchContents=\\{{ vertical: true }}>
|
|
9762
|
+
<ExpoUIText
|
|
9763
|
+
textStyle=\\{{ color: theme.text, fontSize: 16, fontWeight: "bold" }}
|
|
9764
|
+
>
|
|
9765
|
+
Private Data
|
|
9766
|
+
</ExpoUIText>
|
|
9767
|
+
</Host>
|
|
9416
9768
|
{privateData && (
|
|
9417
|
-
<
|
|
9418
|
-
|
|
9419
|
-
|
|
9769
|
+
<Host matchContents=\\{{ vertical: true }}>
|
|
9770
|
+
<ExpoUIText
|
|
9771
|
+
textStyle=\\{{ color: theme.text, fontSize: 14 }}
|
|
9772
|
+
style=\\{{ opacity: 0.7 }}
|
|
9773
|
+
>
|
|
9774
|
+
{privateData.data?.message ?? ""}
|
|
9775
|
+
</ExpoUIText>
|
|
9776
|
+
</Host>
|
|
9420
9777
|
)}
|
|
9421
9778
|
</View>
|
|
9422
9779
|
{{/unless}}
|
|
@@ -9438,45 +9795,34 @@ scrollView: {
|
|
|
9438
9795
|
flex: 1,
|
|
9439
9796
|
},
|
|
9440
9797
|
content: {
|
|
9441
|
-
|
|
9798
|
+
paddingHorizontal: 20,
|
|
9799
|
+
paddingTop: 28,
|
|
9800
|
+
paddingBottom: 32,
|
|
9442
9801
|
},
|
|
9443
|
-
|
|
9444
|
-
|
|
9445
|
-
|
|
9446
|
-
marginBottom:
|
|
9802
|
+
titleHost: {
|
|
9803
|
+
alignSelf: "stretch",
|
|
9804
|
+
height: 34,
|
|
9805
|
+
marginBottom: 24,
|
|
9447
9806
|
},
|
|
9448
9807
|
userCard: {
|
|
9449
9808
|
marginBottom: 16,
|
|
9450
9809
|
padding: 16,
|
|
9451
9810
|
borderWidth: 1,
|
|
9811
|
+
borderRadius: 16,
|
|
9452
9812
|
},
|
|
9453
9813
|
userHeader: {
|
|
9454
9814
|
marginBottom: 8,
|
|
9455
9815
|
},
|
|
9456
|
-
|
|
9457
|
-
|
|
9458
|
-
},
|
|
9459
|
-
userName: {
|
|
9460
|
-
fontWeight: "bold",
|
|
9461
|
-
},
|
|
9462
|
-
userEmail: {
|
|
9463
|
-
fontSize: 14,
|
|
9464
|
-
marginBottom: 12,
|
|
9465
|
-
},
|
|
9466
|
-
signOutButton: {
|
|
9467
|
-
padding: 12,
|
|
9468
|
-
},
|
|
9469
|
-
signOutText: {
|
|
9470
|
-
color: "#ffffff",
|
|
9816
|
+
paymentActions: {
|
|
9817
|
+
marginTop: 12,
|
|
9471
9818
|
},
|
|
9472
9819
|
statusCard: {
|
|
9473
9820
|
marginBottom: 16,
|
|
9474
9821
|
padding: 16,
|
|
9475
9822
|
borderWidth: 1,
|
|
9823
|
+
borderRadius: 16,
|
|
9476
9824
|
},
|
|
9477
|
-
|
|
9478
|
-
fontSize: 16,
|
|
9479
|
-
fontWeight: "bold",
|
|
9825
|
+
cardTitleHost: {
|
|
9480
9826
|
marginBottom: 12,
|
|
9481
9827
|
},
|
|
9482
9828
|
statusRow: {
|
|
@@ -9491,22 +9837,14 @@ width: 8,
|
|
|
9491
9837
|
statusContent: {
|
|
9492
9838
|
flex: 1,
|
|
9493
9839
|
},
|
|
9494
|
-
statusTitle: {
|
|
9495
|
-
fontSize: 14,
|
|
9496
|
-
fontWeight: "bold",
|
|
9497
|
-
},
|
|
9498
|
-
statusText: {
|
|
9499
|
-
fontSize: 12,
|
|
9500
|
-
},
|
|
9501
9840
|
privateDataCard: {
|
|
9502
9841
|
marginBottom: 16,
|
|
9503
9842
|
padding: 16,
|
|
9504
9843
|
borderWidth: 1,
|
|
9844
|
+
borderRadius: 16,
|
|
9505
9845
|
},
|
|
9506
|
-
|
|
9507
|
-
|
|
9508
|
-
},
|
|
9509
|
-
});`],
|
|
9846
|
+
});
|
|
9847
|
+
`],
|
|
9510
9848
|
["auth/better-auth/native/bare/components/sign-in.tsx.hbs", `import { authClient } from "@/lib/auth-client";
|
|
9511
9849
|
{{#if (eq api "trpc")}}
|
|
9512
9850
|
import { queryClient } from "@/utils/trpc";
|
|
@@ -10025,9 +10363,41 @@ export const authClient = createAuthClient({
|
|
|
10025
10363
|
}),
|
|
10026
10364
|
],
|
|
10027
10365
|
});
|
|
10366
|
+
{{#if (eq payments "polar")}}
|
|
10367
|
+
|
|
10368
|
+
type PolarLinkResponse = {
|
|
10369
|
+
url: string;
|
|
10370
|
+
redirect: boolean;
|
|
10371
|
+
};
|
|
10372
|
+
|
|
10373
|
+
type PolarClientResponse<T> = Promise<{
|
|
10374
|
+
data: T | null;
|
|
10375
|
+
error: { message?: string } | null;
|
|
10376
|
+
}>;
|
|
10377
|
+
|
|
10378
|
+
type PolarNativeClient = typeof authClient & {
|
|
10379
|
+
checkout: (data: {
|
|
10380
|
+
slug?: string;
|
|
10381
|
+
products?: string[] | string;
|
|
10382
|
+
redirect?: boolean;
|
|
10383
|
+
successUrl?: string;
|
|
10384
|
+
returnUrl?: string;
|
|
10385
|
+
}) => PolarClientResponse<PolarLinkResponse>;
|
|
10386
|
+
customer: {
|
|
10387
|
+
portal: (data?: { redirect?: boolean }) => PolarClientResponse<PolarLinkResponse>;
|
|
10388
|
+
};
|
|
10389
|
+
};
|
|
10390
|
+
|
|
10391
|
+
export const polarNativeClient = authClient as PolarNativeClient;
|
|
10392
|
+
{{/if}}
|
|
10028
10393
|
`],
|
|
10029
|
-
["auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs", `import { authClient } from "@/lib/auth-client";
|
|
10030
|
-
import { ScrollView, Text, TouchableOpacity, View } from "react-native";
|
|
10394
|
+
["auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs", `import { authClient{{#if (eq payments "polar")}}, polarNativeClient{{/if}} } from "@/lib/auth-client";
|
|
10395
|
+
import { ScrollView, Text, TouchableOpacity, View{{#if (eq payments "polar")}}, Alert{{/if}} } from "react-native";
|
|
10396
|
+
{{#if (eq payments "polar")}}
|
|
10397
|
+
import * as Linking from "expo-linking";
|
|
10398
|
+
import * as WebBrowser from "expo-web-browser";
|
|
10399
|
+
import { env } from "@{{projectName}}/env/native";
|
|
10400
|
+
{{/if}}
|
|
10031
10401
|
import { StyleSheet } from "react-native-unistyles";
|
|
10032
10402
|
|
|
10033
10403
|
import { Container } from "@/components/container";
|
|
@@ -10052,6 +10422,48 @@ export default function Home() {
|
|
|
10052
10422
|
const privateData = useQuery(trpc.privateData.queryOptions());
|
|
10053
10423
|
{{/if}}
|
|
10054
10424
|
const { data: session } = authClient.useSession();
|
|
10425
|
+
{{#if (eq payments "polar")}}
|
|
10426
|
+
|
|
10427
|
+
const openPolarLink = async (url: string, returnUrl: string) => {
|
|
10428
|
+
await WebBrowser.openAuthSessionAsync(url, returnUrl);
|
|
10429
|
+
};
|
|
10430
|
+
|
|
10431
|
+
const getPolarReturnUrl = (returnUrl: string) => {
|
|
10432
|
+
const url = new URL("/polar/success", env.EXPO_PUBLIC_SERVER_URL);
|
|
10433
|
+
url.searchParams.set("returnUrl", returnUrl);
|
|
10434
|
+
return url.toString();
|
|
10435
|
+
};
|
|
10436
|
+
|
|
10437
|
+
const handlePolarCheckout = async () => {
|
|
10438
|
+
const returnUrl = Linking.createURL("/");
|
|
10439
|
+
const polarReturnUrl = getPolarReturnUrl(returnUrl);
|
|
10440
|
+
const { data, error } = await polarNativeClient.checkout({
|
|
10441
|
+
slug: "pro",
|
|
10442
|
+
redirect: false,
|
|
10443
|
+
successUrl: polarReturnUrl,
|
|
10444
|
+
returnUrl: polarReturnUrl,
|
|
10445
|
+
});
|
|
10446
|
+
|
|
10447
|
+
if (error || !data?.url) {
|
|
10448
|
+
Alert.alert("Checkout unavailable", error?.message ?? "Unable to create a checkout session.");
|
|
10449
|
+
return;
|
|
10450
|
+
}
|
|
10451
|
+
|
|
10452
|
+
await openPolarLink(data.url, returnUrl);
|
|
10453
|
+
};
|
|
10454
|
+
|
|
10455
|
+
const handlePolarPortal = async () => {
|
|
10456
|
+
const returnUrl = Linking.createURL("/");
|
|
10457
|
+
const { data, error } = await polarNativeClient.customer.portal({ redirect: false });
|
|
10458
|
+
|
|
10459
|
+
if (error || !data?.url) {
|
|
10460
|
+
Alert.alert("Portal unavailable", error?.message ?? "Unable to open the customer portal.");
|
|
10461
|
+
return;
|
|
10462
|
+
}
|
|
10463
|
+
|
|
10464
|
+
await openPolarLink(data.url, returnUrl);
|
|
10465
|
+
};
|
|
10466
|
+
{{/if}}
|
|
10055
10467
|
|
|
10056
10468
|
return (
|
|
10057
10469
|
<Container>
|
|
@@ -10082,6 +10494,22 @@ export default function Home() {
|
|
|
10082
10494
|
>
|
|
10083
10495
|
<Text style={styles.signOutButtonText}>Sign Out</Text>
|
|
10084
10496
|
</TouchableOpacity>
|
|
10497
|
+
{{#if (eq payments "polar")}}
|
|
10498
|
+
<View style={styles.paymentActions}>
|
|
10499
|
+
<TouchableOpacity
|
|
10500
|
+
style={styles.polarPrimaryButton}
|
|
10501
|
+
onPress={handlePolarCheckout}
|
|
10502
|
+
>
|
|
10503
|
+
<Text style={styles.polarPrimaryButtonText}>Upgrade to Pro</Text>
|
|
10504
|
+
</TouchableOpacity>
|
|
10505
|
+
<TouchableOpacity
|
|
10506
|
+
style={styles.polarSecondaryButton}
|
|
10507
|
+
onPress={handlePolarPortal}
|
|
10508
|
+
>
|
|
10509
|
+
<Text style={styles.polarSecondaryButtonText}>Manage Subscription</Text>
|
|
10510
|
+
</TouchableOpacity>
|
|
10511
|
+
</View>
|
|
10512
|
+
{{/if}}
|
|
10085
10513
|
</View>
|
|
10086
10514
|
) : null}
|
|
10087
10515
|
{{#unless (eq api "none")}}
|
|
@@ -10174,6 +10602,32 @@ const styles = StyleSheet.create((theme) => ({
|
|
|
10174
10602
|
signOutButtonText: {
|
|
10175
10603
|
fontWeight: "500",
|
|
10176
10604
|
},
|
|
10605
|
+
paymentActions: {
|
|
10606
|
+
marginTop: 12,
|
|
10607
|
+
gap: 8,
|
|
10608
|
+
alignItems: "flex-start",
|
|
10609
|
+
},
|
|
10610
|
+
polarPrimaryButton: {
|
|
10611
|
+
backgroundColor: theme?.colors?.primary,
|
|
10612
|
+
paddingVertical: 8,
|
|
10613
|
+
paddingHorizontal: 16,
|
|
10614
|
+
borderRadius: 6,
|
|
10615
|
+
},
|
|
10616
|
+
polarPrimaryButtonText: {
|
|
10617
|
+
color: theme?.colors?.primaryForeground,
|
|
10618
|
+
fontWeight: "500",
|
|
10619
|
+
},
|
|
10620
|
+
polarSecondaryButton: {
|
|
10621
|
+
borderWidth: 1,
|
|
10622
|
+
borderColor: theme?.colors?.border,
|
|
10623
|
+
paddingVertical: 8,
|
|
10624
|
+
paddingHorizontal: 16,
|
|
10625
|
+
borderRadius: 6,
|
|
10626
|
+
},
|
|
10627
|
+
polarSecondaryButtonText: {
|
|
10628
|
+
color: theme?.colors?.typography,
|
|
10629
|
+
fontWeight: "500",
|
|
10630
|
+
},
|
|
10177
10631
|
apiStatusCard: {
|
|
10178
10632
|
marginBottom: 24,
|
|
10179
10633
|
borderRadius: 8,
|
|
@@ -10678,9 +11132,14 @@ const styles = StyleSheet.create((theme) => ({
|
|
|
10678
11132
|
},
|
|
10679
11133
|
}));
|
|
10680
11134
|
`],
|
|
10681
|
-
["auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs", `import { Text, View, Pressable } from "react-native";
|
|
11135
|
+
["auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs", `import { Text, View, Pressable{{#if (eq payments "polar")}}, Alert{{/if}} } from "react-native";
|
|
11136
|
+
{{#if (eq payments "polar")}}
|
|
11137
|
+
import * as Linking from "expo-linking";
|
|
11138
|
+
import * as WebBrowser from "expo-web-browser";
|
|
11139
|
+
import { env } from "@{{projectName}}/env/native";
|
|
11140
|
+
{{/if}}
|
|
10682
11141
|
import { Container } from "@/components/container";
|
|
10683
|
-
import { authClient } from "@/lib/auth-client";
|
|
11142
|
+
import { authClient{{#if (eq payments "polar")}}, polarNativeClient{{/if}} } from "@/lib/auth-client";
|
|
10684
11143
|
import { Ionicons } from "@expo/vector-icons";
|
|
10685
11144
|
import { Card, Chip, useThemeColor } from "heroui-native";
|
|
10686
11145
|
import { SignIn } from "@/components/sign-in";
|
|
@@ -10708,6 +11167,48 @@ const isConnected = healthCheck?.data === "OK";
|
|
|
10708
11167
|
const isLoading = healthCheck?.isLoading;
|
|
10709
11168
|
{{/if}}
|
|
10710
11169
|
const { data: session } = authClient.useSession();
|
|
11170
|
+
{{#if (eq payments "polar")}}
|
|
11171
|
+
|
|
11172
|
+
const openPolarLink = async (url: string, returnUrl: string) => {
|
|
11173
|
+
await WebBrowser.openAuthSessionAsync(url, returnUrl);
|
|
11174
|
+
};
|
|
11175
|
+
|
|
11176
|
+
const getPolarReturnUrl = (returnUrl: string) => {
|
|
11177
|
+
const url = new URL("/polar/success", env.EXPO_PUBLIC_SERVER_URL);
|
|
11178
|
+
url.searchParams.set("returnUrl", returnUrl);
|
|
11179
|
+
return url.toString();
|
|
11180
|
+
};
|
|
11181
|
+
|
|
11182
|
+
const handlePolarCheckout = async () => {
|
|
11183
|
+
const returnUrl = Linking.createURL("/");
|
|
11184
|
+
const polarReturnUrl = getPolarReturnUrl(returnUrl);
|
|
11185
|
+
const { data, error } = await polarNativeClient.checkout({
|
|
11186
|
+
slug: "pro",
|
|
11187
|
+
redirect: false,
|
|
11188
|
+
successUrl: polarReturnUrl,
|
|
11189
|
+
returnUrl: polarReturnUrl,
|
|
11190
|
+
});
|
|
11191
|
+
|
|
11192
|
+
if (error || !data?.url) {
|
|
11193
|
+
Alert.alert("Checkout unavailable", error?.message ?? "Unable to create a checkout session.");
|
|
11194
|
+
return;
|
|
11195
|
+
}
|
|
11196
|
+
|
|
11197
|
+
await openPolarLink(data.url, returnUrl);
|
|
11198
|
+
};
|
|
11199
|
+
|
|
11200
|
+
const handlePolarPortal = async () => {
|
|
11201
|
+
const returnUrl = Linking.createURL("/");
|
|
11202
|
+
const { data, error } = await polarNativeClient.customer.portal({ redirect: false });
|
|
11203
|
+
|
|
11204
|
+
if (error || !data?.url) {
|
|
11205
|
+
Alert.alert("Portal unavailable", error?.message ?? "Unable to open the customer portal.");
|
|
11206
|
+
return;
|
|
11207
|
+
}
|
|
11208
|
+
|
|
11209
|
+
await openPolarLink(data.url, returnUrl);
|
|
11210
|
+
};
|
|
11211
|
+
{{/if}}
|
|
10711
11212
|
|
|
10712
11213
|
const mutedColor = useThemeColor("muted");
|
|
10713
11214
|
const successColor = useThemeColor("success");
|
|
@@ -10742,6 +11243,22 @@ return (
|
|
|
10742
11243
|
>
|
|
10743
11244
|
<Text className="text-foreground font-medium">Sign Out</Text>
|
|
10744
11245
|
</Pressable>
|
|
11246
|
+
{{#if (eq payments "polar")}}
|
|
11247
|
+
<View className="mt-4 gap-3">
|
|
11248
|
+
<Pressable
|
|
11249
|
+
className="bg-primary py-3 px-4 rounded-lg self-start active:opacity-70"
|
|
11250
|
+
onPress={handlePolarCheckout}
|
|
11251
|
+
>
|
|
11252
|
+
<Text className="text-foreground font-medium">Upgrade to Pro</Text>
|
|
11253
|
+
</Pressable>
|
|
11254
|
+
<Pressable
|
|
11255
|
+
className="border border-border py-3 px-4 rounded-lg self-start active:opacity-70"
|
|
11256
|
+
onPress={handlePolarPortal}
|
|
11257
|
+
>
|
|
11258
|
+
<Text className="text-foreground font-medium">Manage Subscription</Text>
|
|
11259
|
+
</Pressable>
|
|
11260
|
+
</View>
|
|
11261
|
+
{{/if}}
|
|
10745
11262
|
</Card>
|
|
10746
11263
|
) : null}
|
|
10747
11264
|
|
|
@@ -10800,7 +11317,8 @@ return (
|
|
|
10800
11317
|
)}
|
|
10801
11318
|
</Container>
|
|
10802
11319
|
);
|
|
10803
|
-
}
|
|
11320
|
+
}
|
|
11321
|
+
`],
|
|
10804
11322
|
["auth/better-auth/native/uniwind/components/sign-in.tsx.hbs", `import { authClient } from "@/lib/auth-client";
|
|
10805
11323
|
{{#if (eq api "trpc")}}
|
|
10806
11324
|
import { queryClient } from "@/utils/trpc";
|
|
@@ -11259,14 +11777,8 @@ export function createAuth({{#if (and (eq backend "self") (eq webDeploy "cloudfl
|
|
|
11259
11777
|
env.CORS_ORIGIN,
|
|
11260
11778
|
{{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
|
|
11261
11779
|
"{{projectName}}://",
|
|
11262
|
-
|
|
11263
|
-
|
|
11264
|
-
"exp://",
|
|
11265
|
-
"exp://**",
|
|
11266
|
-
"exp://192.168.*.*:*/**",
|
|
11267
|
-
"http://localhost:8081",
|
|
11268
|
-
]
|
|
11269
|
-
: []),
|
|
11780
|
+
"exp://",
|
|
11781
|
+
"http://localhost:8081",
|
|
11270
11782
|
{{/if}}
|
|
11271
11783
|
],
|
|
11272
11784
|
emailAndPassword: {
|
|
@@ -11348,14 +11860,8 @@ export function createAuth({{#if (and (eq backend "self") (eq webDeploy "cloudfl
|
|
|
11348
11860
|
env.CORS_ORIGIN,
|
|
11349
11861
|
{{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
|
|
11350
11862
|
"{{projectName}}://",
|
|
11351
|
-
|
|
11352
|
-
|
|
11353
|
-
"exp://",
|
|
11354
|
-
"exp://**",
|
|
11355
|
-
"exp://192.168.*.*:*/**",
|
|
11356
|
-
"http://localhost:8081",
|
|
11357
|
-
]
|
|
11358
|
-
: []),
|
|
11863
|
+
"exp://",
|
|
11864
|
+
"http://localhost:8081",
|
|
11359
11865
|
{{/if}}
|
|
11360
11866
|
],
|
|
11361
11867
|
emailAndPassword: {
|
|
@@ -11428,14 +11934,8 @@ export function createAuth() {
|
|
|
11428
11934
|
env.CORS_ORIGIN,
|
|
11429
11935
|
{{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
|
|
11430
11936
|
"{{projectName}}://",
|
|
11431
|
-
|
|
11432
|
-
|
|
11433
|
-
"exp://",
|
|
11434
|
-
"exp://**",
|
|
11435
|
-
"exp://192.168.*.*:*/**",
|
|
11436
|
-
"http://localhost:8081",
|
|
11437
|
-
]
|
|
11438
|
-
: []),
|
|
11937
|
+
"exp://",
|
|
11938
|
+
"http://localhost:8081",
|
|
11439
11939
|
{{/if}}
|
|
11440
11940
|
],
|
|
11441
11941
|
emailAndPassword: {
|
|
@@ -11515,14 +12015,8 @@ export function createAuth({{#if (and (eq backend "self") (eq webDeploy "cloudfl
|
|
|
11515
12015
|
env.CORS_ORIGIN,
|
|
11516
12016
|
{{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
|
|
11517
12017
|
"{{projectName}}://",
|
|
11518
|
-
|
|
11519
|
-
|
|
11520
|
-
"exp://",
|
|
11521
|
-
"exp://**",
|
|
11522
|
-
"exp://192.168.*.*:*/**",
|
|
11523
|
-
"http://localhost:8081",
|
|
11524
|
-
]
|
|
11525
|
-
: []),
|
|
12018
|
+
"exp://",
|
|
12019
|
+
"http://localhost:8081",
|
|
11526
12020
|
{{/if}}
|
|
11527
12021
|
],
|
|
11528
12022
|
emailAndPassword: {
|
|
@@ -11593,14 +12087,8 @@ export function createAuth({{#if (and (eq backend "self") (eq webDeploy "cloudfl
|
|
|
11593
12087
|
env.CORS_ORIGIN,
|
|
11594
12088
|
{{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
|
|
11595
12089
|
"{{projectName}}://",
|
|
11596
|
-
|
|
11597
|
-
|
|
11598
|
-
"exp://",
|
|
11599
|
-
"exp://**",
|
|
11600
|
-
"exp://192.168.*.*:*/**",
|
|
11601
|
-
"http://localhost:8081",
|
|
11602
|
-
]
|
|
11603
|
-
: []),
|
|
12090
|
+
"exp://",
|
|
12091
|
+
"http://localhost:8081",
|
|
11604
12092
|
{{/if}}
|
|
11605
12093
|
],
|
|
11606
12094
|
emailAndPassword: {
|
|
@@ -11963,40 +12451,42 @@ export const accountRelations = relations(account, ({ one }) => ({
|
|
|
11963
12451
|
["auth/better-auth/server/db/mongoose/mongodb/src/models/auth.model.ts.hbs", `import mongoose from 'mongoose';
|
|
11964
12452
|
|
|
11965
12453
|
const { Schema, model } = mongoose;
|
|
12454
|
+
const { ObjectId } = Schema.Types;
|
|
11966
12455
|
|
|
11967
12456
|
const userSchema = new Schema(
|
|
11968
12457
|
{
|
|
11969
|
-
_id: { type:
|
|
12458
|
+
_id: { type: ObjectId, auto: true },
|
|
11970
12459
|
name: { type: String, required: true },
|
|
11971
12460
|
email: { type: String, required: true, unique: true },
|
|
11972
|
-
emailVerified: { type: Boolean, required: true },
|
|
12461
|
+
emailVerified: { type: Boolean, required: true, default: false },
|
|
11973
12462
|
image: { type: String },
|
|
11974
|
-
createdAt: { type: Date, required: true },
|
|
11975
|
-
updatedAt: { type: Date, required: true },
|
|
12463
|
+
createdAt: { type: Date, required: true, default: Date.now },
|
|
12464
|
+
updatedAt: { type: Date, required: true, default: Date.now },
|
|
11976
12465
|
},
|
|
11977
12466
|
{ collection: 'user' }
|
|
11978
12467
|
);
|
|
11979
12468
|
|
|
11980
12469
|
const sessionSchema = new Schema(
|
|
11981
12470
|
{
|
|
11982
|
-
_id: { type:
|
|
12471
|
+
_id: { type: ObjectId, auto: true },
|
|
11983
12472
|
expiresAt: { type: Date, required: true },
|
|
11984
12473
|
token: { type: String, required: true, unique: true },
|
|
11985
|
-
createdAt: { type: Date, required: true },
|
|
11986
|
-
updatedAt: { type: Date, required: true },
|
|
12474
|
+
createdAt: { type: Date, required: true, default: Date.now },
|
|
12475
|
+
updatedAt: { type: Date, required: true, default: Date.now },
|
|
11987
12476
|
ipAddress: { type: String },
|
|
11988
12477
|
userAgent: { type: String },
|
|
11989
|
-
userId: { type:
|
|
12478
|
+
userId: { type: ObjectId, ref: 'User', required: true },
|
|
11990
12479
|
},
|
|
11991
12480
|
{ collection: 'session' }
|
|
11992
12481
|
);
|
|
12482
|
+
sessionSchema.index({ userId: 1 });
|
|
11993
12483
|
|
|
11994
12484
|
const accountSchema = new Schema(
|
|
11995
12485
|
{
|
|
11996
|
-
_id: { type:
|
|
12486
|
+
_id: { type: ObjectId, auto: true },
|
|
11997
12487
|
accountId: { type: String, required: true },
|
|
11998
12488
|
providerId: { type: String, required: true },
|
|
11999
|
-
userId: { type:
|
|
12489
|
+
userId: { type: ObjectId, ref: 'User', required: true },
|
|
12000
12490
|
accessToken: { type: String },
|
|
12001
12491
|
refreshToken: { type: String },
|
|
12002
12492
|
idToken: { type: String },
|
|
@@ -12004,23 +12494,25 @@ const accountSchema = new Schema(
|
|
|
12004
12494
|
refreshTokenExpiresAt: { type: Date },
|
|
12005
12495
|
scope: { type: String },
|
|
12006
12496
|
password: { type: String },
|
|
12007
|
-
createdAt: { type: Date, required: true },
|
|
12008
|
-
updatedAt: { type: Date, required: true },
|
|
12497
|
+
createdAt: { type: Date, required: true, default: Date.now },
|
|
12498
|
+
updatedAt: { type: Date, required: true, default: Date.now },
|
|
12009
12499
|
},
|
|
12010
12500
|
{ collection: 'account' }
|
|
12011
12501
|
);
|
|
12502
|
+
accountSchema.index({ userId: 1 });
|
|
12012
12503
|
|
|
12013
12504
|
const verificationSchema = new Schema(
|
|
12014
12505
|
{
|
|
12015
|
-
_id: { type:
|
|
12506
|
+
_id: { type: ObjectId, auto: true },
|
|
12016
12507
|
identifier: { type: String, required: true },
|
|
12017
12508
|
value: { type: String, required: true },
|
|
12018
12509
|
expiresAt: { type: Date, required: true },
|
|
12019
|
-
createdAt: { type: Date },
|
|
12020
|
-
updatedAt: { type: Date },
|
|
12510
|
+
createdAt: { type: Date, required: true, default: Date.now },
|
|
12511
|
+
updatedAt: { type: Date, required: true, default: Date.now },
|
|
12021
12512
|
},
|
|
12022
12513
|
{ collection: 'verification' }
|
|
12023
12514
|
);
|
|
12515
|
+
verificationSchema.index({ identifier: 1 });
|
|
12024
12516
|
|
|
12025
12517
|
const User = model('User', userSchema);
|
|
12026
12518
|
const Session = model('Session', sessionSchema);
|
|
@@ -12483,7 +12975,7 @@ import { authClient } from "../lib/auth-client";
|
|
|
12483
12975
|
`],
|
|
12484
12976
|
["auth/better-auth/web/astro/src/lib/auth-client.ts.hbs", `import { createAuthClient } from "better-auth/client";
|
|
12485
12977
|
{{#if (eq payments "polar")}}
|
|
12486
|
-
import { polarClient } from "@polar-sh/better-auth";
|
|
12978
|
+
import { polarClient } from "@polar-sh/better-auth/client";
|
|
12487
12979
|
{{/if}}
|
|
12488
12980
|
{{#if (ne backend "self")}}
|
|
12489
12981
|
import { PUBLIC_SERVER_URL } from "astro:env/client";
|
|
@@ -13019,7 +13511,7 @@ watchEffect(() => {
|
|
|
13019
13511
|
`],
|
|
13020
13512
|
["auth/better-auth/web/nuxt/app/plugins/auth-client.ts.hbs", `import { createAuthClient } from "better-auth/vue";
|
|
13021
13513
|
{{#if (eq payments "polar")}}
|
|
13022
|
-
import { polarClient } from "@polar-sh/better-auth";
|
|
13514
|
+
import { polarClient } from "@polar-sh/better-auth/client";
|
|
13023
13515
|
{{/if}}
|
|
13024
13516
|
|
|
13025
13517
|
export default defineNuxtPlugin(() => {
|
|
@@ -13045,7 +13537,7 @@ export default defineNuxtPlugin(() => {
|
|
|
13045
13537
|
`],
|
|
13046
13538
|
["auth/better-auth/web/react/base/src/lib/auth-client.ts.hbs", `import { createAuthClient } from "better-auth/react";
|
|
13047
13539
|
{{#if (eq payments "polar")}}
|
|
13048
|
-
import { polarClient } from "@polar-sh/better-auth";
|
|
13540
|
+
import { polarClient } from "@polar-sh/better-auth/client";
|
|
13049
13541
|
{{/if}}
|
|
13050
13542
|
{{#unless (eq backend "self")}}
|
|
13051
13543
|
import { env } from "@{{projectName}}/env/web";
|
|
@@ -15291,7 +15783,7 @@ export default function UserMenu() {
|
|
|
15291
15783
|
`],
|
|
15292
15784
|
["auth/better-auth/web/solid/src/lib/auth-client.ts.hbs", `import { createAuthClient } from "better-auth/solid";
|
|
15293
15785
|
{{#if (eq payments "polar")}}
|
|
15294
|
-
import { polarClient } from "@polar-sh/better-auth";
|
|
15786
|
+
import { polarClient } from "@polar-sh/better-auth/client";
|
|
15295
15787
|
{{/if}}
|
|
15296
15788
|
import { env } from "@{{projectName}}/env/web";
|
|
15297
15789
|
|
|
@@ -15709,7 +16201,7 @@ import { PUBLIC_SERVER_URL } from "$env/static/public";
|
|
|
15709
16201
|
{{/unless}}
|
|
15710
16202
|
import { createAuthClient } from "better-auth/svelte";
|
|
15711
16203
|
{{#if (eq payments "polar")}}
|
|
15712
|
-
import { polarClient } from "@polar-sh/better-auth";
|
|
16204
|
+
import { polarClient } from "@polar-sh/better-auth/client";
|
|
15713
16205
|
{{/if}}
|
|
15714
16206
|
|
|
15715
16207
|
export const authClient = createAuthClient({
|
|
@@ -17295,11 +17787,90 @@ export const startInstance = createStart(() => {
|
|
|
17295
17787
|
`],
|
|
17296
17788
|
["backend/convex/packages/backend/_gitignore", `
|
|
17297
17789
|
.env.local
|
|
17790
|
+
`],
|
|
17791
|
+
["backend/convex/packages/backend/convex/_generated/api.d.ts", `/* eslint-disable */
|
|
17792
|
+
export declare const api: any;
|
|
17793
|
+
export declare const internal: any;
|
|
17794
|
+
export declare const components: any;
|
|
17795
|
+
`],
|
|
17796
|
+
["backend/convex/packages/backend/convex/_generated/api.js", `/* eslint-disable */
|
|
17797
|
+
import { anyApi, componentsGeneric } from "convex/server";
|
|
17798
|
+
|
|
17799
|
+
export const api = anyApi;
|
|
17800
|
+
export const internal = anyApi;
|
|
17801
|
+
export const components = componentsGeneric();
|
|
17802
|
+
`],
|
|
17803
|
+
["backend/convex/packages/backend/convex/_generated/dataModel.d.ts", `/* eslint-disable */
|
|
17804
|
+
import type {
|
|
17805
|
+
DataModelFromSchemaDefinition,
|
|
17806
|
+
DocumentByName,
|
|
17807
|
+
SystemTableNames,
|
|
17808
|
+
TableNamesInDataModel,
|
|
17809
|
+
} from "convex/server";
|
|
17810
|
+
import type { GenericId } from "convex/values";
|
|
17811
|
+
|
|
17812
|
+
import schema from "../schema.js";
|
|
17813
|
+
|
|
17814
|
+
export type DataModel = DataModelFromSchemaDefinition<typeof schema>;
|
|
17815
|
+
export type TableNames = TableNamesInDataModel<DataModel>;
|
|
17816
|
+
export type Doc<TableName extends TableNames> = DocumentByName<DataModel, TableName>;
|
|
17817
|
+
export type Id<TableName extends TableNames | SystemTableNames> = GenericId<TableName>;
|
|
17818
|
+
`],
|
|
17819
|
+
["backend/convex/packages/backend/convex/_generated/server.d.ts", `/* eslint-disable */
|
|
17820
|
+
import type {
|
|
17821
|
+
ActionBuilder,
|
|
17822
|
+
GenericActionCtx,
|
|
17823
|
+
GenericDatabaseReader,
|
|
17824
|
+
GenericDatabaseWriter,
|
|
17825
|
+
GenericMutationCtx,
|
|
17826
|
+
GenericQueryCtx,
|
|
17827
|
+
HttpActionBuilder,
|
|
17828
|
+
MutationBuilder,
|
|
17829
|
+
QueryBuilder,
|
|
17830
|
+
} from "convex/server";
|
|
17831
|
+
|
|
17832
|
+
import type { DataModel } from "./dataModel.js";
|
|
17833
|
+
|
|
17834
|
+
export declare const query: QueryBuilder<DataModel, "public">;
|
|
17835
|
+
export declare const internalQuery: QueryBuilder<DataModel, "internal">;
|
|
17836
|
+
export declare const mutation: MutationBuilder<DataModel, "public">;
|
|
17837
|
+
export declare const internalMutation: MutationBuilder<DataModel, "internal">;
|
|
17838
|
+
export declare const action: ActionBuilder<DataModel, "public">;
|
|
17839
|
+
export declare const internalAction: ActionBuilder<DataModel, "internal">;
|
|
17840
|
+
export declare const httpAction: HttpActionBuilder;
|
|
17841
|
+
|
|
17842
|
+
export type QueryCtx = GenericQueryCtx<DataModel>;
|
|
17843
|
+
export type MutationCtx = GenericMutationCtx<DataModel>;
|
|
17844
|
+
export type ActionCtx = GenericActionCtx<DataModel>;
|
|
17845
|
+
export type DatabaseReader = GenericDatabaseReader<DataModel>;
|
|
17846
|
+
export type DatabaseWriter = GenericDatabaseWriter<DataModel>;
|
|
17847
|
+
`],
|
|
17848
|
+
["backend/convex/packages/backend/convex/_generated/server.js", `/* eslint-disable */
|
|
17849
|
+
import {
|
|
17850
|
+
actionGeneric,
|
|
17851
|
+
httpActionGeneric,
|
|
17852
|
+
internalActionGeneric,
|
|
17853
|
+
internalMutationGeneric,
|
|
17854
|
+
internalQueryGeneric,
|
|
17855
|
+
mutationGeneric,
|
|
17856
|
+
queryGeneric,
|
|
17857
|
+
} from "convex/server";
|
|
17858
|
+
|
|
17859
|
+
export const query = queryGeneric;
|
|
17860
|
+
export const internalQuery = internalQueryGeneric;
|
|
17861
|
+
export const mutation = mutationGeneric;
|
|
17862
|
+
export const internalMutation = internalMutationGeneric;
|
|
17863
|
+
export const action = actionGeneric;
|
|
17864
|
+
export const internalAction = internalActionGeneric;
|
|
17865
|
+
export const httpAction = httpActionGeneric;
|
|
17298
17866
|
`],
|
|
17299
17867
|
["backend/convex/packages/backend/convex/convex.config.ts.hbs", `import { defineApp } from "convex/server";
|
|
17300
17868
|
{{#if (eq auth "better-auth")}}
|
|
17301
17869
|
import betterAuth from "@convex-dev/better-auth/convex.config";
|
|
17302
17870
|
{{/if}}
|
|
17871
|
+
{{#if (eq payments "polar")}}
|
|
17872
|
+
import polar from "@convex-dev/polar/convex.config.js";
|
|
17873
|
+
{{/if}}
|
|
17303
17874
|
{{#if (includes examples "ai")}}
|
|
17304
17875
|
import agent from "@convex-dev/agent/convex.config";
|
|
17305
17876
|
{{/if}}
|
|
@@ -17308,6 +17879,9 @@ const app = defineApp();
|
|
|
17308
17879
|
{{#if (eq auth "better-auth")}}
|
|
17309
17880
|
app.use(betterAuth);
|
|
17310
17881
|
{{/if}}
|
|
17882
|
+
{{#if (eq payments "polar")}}
|
|
17883
|
+
app.use(polar);
|
|
17884
|
+
{{/if}}
|
|
17311
17885
|
{{#if (includes examples "ai")}}
|
|
17312
17886
|
app.use(agent);
|
|
17313
17887
|
{{/if}}
|
|
@@ -17436,6 +18010,7 @@ export default defineSchema({
|
|
|
17436
18010
|
"jsx": "react-jsx",
|
|
17437
18011
|
"skipLibCheck": true,
|
|
17438
18012
|
"allowSyntheticDefaultImports": true,
|
|
18013
|
+
"types": ["node"],
|
|
17439
18014
|
|
|
17440
18015
|
/* These compiler options are required by Convex */
|
|
17441
18016
|
"target": "ESNext",
|
|
@@ -17630,6 +18205,32 @@ new Elysia()
|
|
|
17630
18205
|
{{/if}}
|
|
17631
18206
|
}),
|
|
17632
18207
|
)
|
|
18208
|
+
{{#if (and (eq auth "better-auth") (eq payments "polar") (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles")))}}
|
|
18209
|
+
.get("/polar/success", ({ request, status }) => {
|
|
18210
|
+
const nativeAppUrl = "{{projectName}}://";
|
|
18211
|
+
const allowedNativeProtocols = new Set(["exp:", new URL(nativeAppUrl).protocol]);
|
|
18212
|
+
const requestUrl = new URL(request.url);
|
|
18213
|
+
const returnUrl = requestUrl.searchParams.get("returnUrl") || nativeAppUrl;
|
|
18214
|
+
|
|
18215
|
+
let redirectUrl: URL;
|
|
18216
|
+
try {
|
|
18217
|
+
redirectUrl = new URL(returnUrl);
|
|
18218
|
+
} catch {
|
|
18219
|
+
return status(400, "Invalid return URL");
|
|
18220
|
+
}
|
|
18221
|
+
|
|
18222
|
+
if (!allowedNativeProtocols.has(redirectUrl.protocol)) {
|
|
18223
|
+
return status(400, "Invalid return URL");
|
|
18224
|
+
}
|
|
18225
|
+
|
|
18226
|
+
return new Response(null, {
|
|
18227
|
+
status: 302,
|
|
18228
|
+
headers: {
|
|
18229
|
+
Location: redirectUrl.toString(),
|
|
18230
|
+
},
|
|
18231
|
+
});
|
|
18232
|
+
})
|
|
18233
|
+
{{/if}}
|
|
17633
18234
|
{{#if (eq auth "better-auth")}}
|
|
17634
18235
|
.all("/api/auth/*", async (context) => {
|
|
17635
18236
|
const { request, status } = context;
|
|
@@ -17712,10 +18313,8 @@ import { ZodToJsonSchemaConverter } from "@orpc/zod/zod4";
|
|
|
17712
18313
|
import { RPCHandler } from "@orpc/server/node";
|
|
17713
18314
|
import { onError } from "@orpc/server";
|
|
17714
18315
|
import { appRouter } from "@{{projectName}}/api/routers/index";
|
|
17715
|
-
{{#if (or (eq auth "better-auth") (eq auth "clerk"))}}
|
|
17716
18316
|
import { createContext } from "@{{projectName}}/api/context";
|
|
17717
18317
|
{{/if}}
|
|
17718
|
-
{{/if}}
|
|
17719
18318
|
import cors from "cors";
|
|
17720
18319
|
import express from "express";
|
|
17721
18320
|
{{#if (includes examples "ai")}}
|
|
@@ -17754,6 +18353,31 @@ app.use(clerkMiddleware());
|
|
|
17754
18353
|
app.all("/api/auth{/*path}", toNodeHandler(auth));
|
|
17755
18354
|
{{/if}}
|
|
17756
18355
|
|
|
18356
|
+
{{#if (and (eq auth "better-auth") (eq payments "polar") (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles")))}}
|
|
18357
|
+
const nativeAppUrl = "{{projectName}}://";
|
|
18358
|
+
const allowedNativeProtocols = new Set(["exp:", new URL(nativeAppUrl).protocol]);
|
|
18359
|
+
|
|
18360
|
+
app.get("/polar/success", (req, res) => {
|
|
18361
|
+
const requestUrl = new URL(req.url, env.BETTER_AUTH_URL);
|
|
18362
|
+
const returnUrl = requestUrl.searchParams.get("returnUrl") || nativeAppUrl;
|
|
18363
|
+
|
|
18364
|
+
let redirectUrl: URL;
|
|
18365
|
+
try {
|
|
18366
|
+
redirectUrl = new URL(returnUrl);
|
|
18367
|
+
} catch {
|
|
18368
|
+
res.status(400).send("Invalid return URL");
|
|
18369
|
+
return;
|
|
18370
|
+
}
|
|
18371
|
+
|
|
18372
|
+
if (!allowedNativeProtocols.has(redirectUrl.protocol)) {
|
|
18373
|
+
res.status(400).send("Invalid return URL");
|
|
18374
|
+
return;
|
|
18375
|
+
}
|
|
18376
|
+
|
|
18377
|
+
res.redirect(302, redirectUrl.toString());
|
|
18378
|
+
});
|
|
18379
|
+
|
|
18380
|
+
{{/if}}
|
|
17757
18381
|
{{#if (eq api "trpc")}}
|
|
17758
18382
|
app.use(
|
|
17759
18383
|
"/trpc",
|
|
@@ -17788,21 +18412,13 @@ const apiHandler = new OpenAPIHandler(appRouter, {
|
|
|
17788
18412
|
app.use(async (req, res, next) => {
|
|
17789
18413
|
const rpcResult = await rpcHandler.handle(req, res, {
|
|
17790
18414
|
prefix: "/rpc",
|
|
17791
|
-
{{#if (or (eq auth "better-auth") (eq auth "clerk"))}}
|
|
17792
18415
|
context: await createContext({ req }),
|
|
17793
|
-
{{else}}
|
|
17794
|
-
context: {},
|
|
17795
|
-
{{/if}}
|
|
17796
18416
|
});
|
|
17797
18417
|
if (rpcResult.matched) return;
|
|
17798
18418
|
|
|
17799
18419
|
const apiResult = await apiHandler.handle(req, res, {
|
|
17800
18420
|
prefix: "/api-reference",
|
|
17801
|
-
{{#if (or (eq auth "better-auth") (eq auth "clerk"))}}
|
|
17802
18421
|
context: await createContext({ req }),
|
|
17803
|
-
{{else}}
|
|
17804
|
-
context: {},
|
|
17805
|
-
{{/if}}
|
|
17806
18422
|
});
|
|
17807
18423
|
if (apiResult.matched) return;
|
|
17808
18424
|
|
|
@@ -17913,9 +18529,37 @@ const fastify = Fastify({
|
|
|
17913
18529
|
|
|
17914
18530
|
fastify.register(fastifyCors, baseCorsConfig);
|
|
17915
18531
|
{{#if (eq auth "clerk")}}
|
|
17916
|
-
fastify.register(clerkPlugin
|
|
18532
|
+
fastify.register(clerkPlugin, {
|
|
18533
|
+
publishableKey: env.CLERK_PUBLISHABLE_KEY,
|
|
18534
|
+
secretKey: env.CLERK_SECRET_KEY,
|
|
18535
|
+
});
|
|
17917
18536
|
{{/if}}
|
|
17918
18537
|
|
|
18538
|
+
{{#if (and (eq auth "better-auth") (eq payments "polar") (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles")))}}
|
|
18539
|
+
const nativeAppUrl = "{{projectName}}://";
|
|
18540
|
+
const allowedNativeProtocols = new Set(["exp:", new URL(nativeAppUrl).protocol]);
|
|
18541
|
+
|
|
18542
|
+
fastify.get("/polar/success", async (request, reply) => {
|
|
18543
|
+
const requestUrl = new URL(request.url, env.BETTER_AUTH_URL);
|
|
18544
|
+
const returnUrl = requestUrl.searchParams.get("returnUrl") || nativeAppUrl;
|
|
18545
|
+
|
|
18546
|
+
let redirectUrl: URL;
|
|
18547
|
+
try {
|
|
18548
|
+
redirectUrl = new URL(returnUrl);
|
|
18549
|
+
} catch {
|
|
18550
|
+
reply.status(400).send("Invalid return URL");
|
|
18551
|
+
return;
|
|
18552
|
+
}
|
|
18553
|
+
|
|
18554
|
+
if (!allowedNativeProtocols.has(redirectUrl.protocol)) {
|
|
18555
|
+
reply.status(400).send("Invalid return URL");
|
|
18556
|
+
return;
|
|
18557
|
+
}
|
|
18558
|
+
|
|
18559
|
+
reply.status(302).header("Location", redirectUrl.toString()).send();
|
|
18560
|
+
});
|
|
18561
|
+
|
|
18562
|
+
{{/if}}
|
|
17919
18563
|
{{#if (eq api "orpc")}}
|
|
17920
18564
|
fastify.register(async (rpcApp) => {
|
|
17921
18565
|
// Fully utilize oRPC features by letting oRPC parse the request body.
|
|
@@ -18090,6 +18734,29 @@ app.on(
|
|
|
18090
18734
|
);
|
|
18091
18735
|
{{/if}}
|
|
18092
18736
|
|
|
18737
|
+
{{#if (and (eq auth "better-auth") (eq payments "polar") (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles")))}}
|
|
18738
|
+
const nativeAppUrl = "{{projectName}}://";
|
|
18739
|
+
const allowedNativeProtocols = new Set(["exp:", new URL(nativeAppUrl).protocol]);
|
|
18740
|
+
|
|
18741
|
+
app.get("/polar/success", (c) => {
|
|
18742
|
+
const requestUrl = new URL(c.req.url);
|
|
18743
|
+
const returnUrl = requestUrl.searchParams.get("returnUrl") || nativeAppUrl;
|
|
18744
|
+
|
|
18745
|
+
let redirectUrl: URL;
|
|
18746
|
+
try {
|
|
18747
|
+
redirectUrl = new URL(returnUrl);
|
|
18748
|
+
} catch {
|
|
18749
|
+
return c.text("Invalid return URL", 400);
|
|
18750
|
+
}
|
|
18751
|
+
|
|
18752
|
+
if (!allowedNativeProtocols.has(redirectUrl.protocol)) {
|
|
18753
|
+
return c.text("Invalid return URL", 400);
|
|
18754
|
+
}
|
|
18755
|
+
|
|
18756
|
+
return c.redirect(redirectUrl.toString(), 302);
|
|
18757
|
+
});
|
|
18758
|
+
|
|
18759
|
+
{{/if}}
|
|
18093
18760
|
{{#if (eq api "orpc")}}
|
|
18094
18761
|
export const apiHandler = new OpenAPIHandler(appRouter, {
|
|
18095
18762
|
plugins: [
|
|
@@ -18678,14 +19345,13 @@ export function createDb() {
|
|
|
18678
19345
|
}
|
|
18679
19346
|
{{/if}}
|
|
18680
19347
|
`],
|
|
19348
|
+
["db/drizzle/sqlite/src/migrations/.gitkeep", ``],
|
|
18681
19349
|
["db/mongoose/mongodb/src/index.ts.hbs", `import mongoose from "mongoose";
|
|
18682
19350
|
import { env } from "@{{projectName}}/env/server";
|
|
18683
19351
|
|
|
18684
|
-
await mongoose.connect(env.DATABASE_URL)
|
|
18685
|
-
console.log("Error connecting to database:", error);
|
|
18686
|
-
});
|
|
19352
|
+
await mongoose.connect(env.DATABASE_URL);
|
|
18687
19353
|
|
|
18688
|
-
const client = mongoose.connection.getClient().db(
|
|
19354
|
+
const client = mongoose.connection.getClient().db();
|
|
18689
19355
|
|
|
18690
19356
|
export { client };
|
|
18691
19357
|
`],
|
|
@@ -19007,6 +19673,7 @@ export default defineConfig({
|
|
|
19007
19673
|
{{/if}}
|
|
19008
19674
|
},
|
|
19009
19675
|
});`],
|
|
19676
|
+
["db/prisma/sqlite/prisma/migrations/.gitkeep", ``],
|
|
19010
19677
|
["db/prisma/sqlite/prisma/schema/schema.prisma.hbs", `generator client {
|
|
19011
19678
|
provider = "prisma-client"
|
|
19012
19679
|
output = "../generated"
|
|
@@ -19252,7 +19919,6 @@ import { Ionicons } from "@expo/vector-icons";
|
|
|
19252
19919
|
import {
|
|
19253
19920
|
useUIMessages,
|
|
19254
19921
|
useSmoothText,
|
|
19255
|
-
type UIMessage,
|
|
19256
19922
|
} from "@convex-dev/agent/react";
|
|
19257
19923
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
19258
19924
|
import { useMutation } from "convex/react";
|
|
@@ -19306,7 +19972,7 @@ export default function AIScreen() {
|
|
|
19306
19972
|
);
|
|
19307
19973
|
|
|
19308
19974
|
const hasStreamingMessage = messages?.some(
|
|
19309
|
-
(m
|
|
19975
|
+
(m) => m.status === "streaming",
|
|
19310
19976
|
);
|
|
19311
19977
|
|
|
19312
19978
|
useEffect(() => {
|
|
@@ -19363,7 +20029,7 @@ export default function AIScreen() {
|
|
|
19363
20029
|
</View>
|
|
19364
20030
|
) : (
|
|
19365
20031
|
<View style={styles.messagesList}>
|
|
19366
|
-
{messages.map((message
|
|
20032
|
+
{messages.map((message) => (
|
|
19367
20033
|
<View
|
|
19368
20034
|
key={message.key}
|
|
19369
20035
|
style={[
|
|
@@ -19383,7 +20049,9 @@ export default function AIScreen() {
|
|
|
19383
20049
|
{message.role === "user" ? "You" : "AI Assistant"}
|
|
19384
20050
|
</Text>
|
|
19385
20051
|
<MessageContent
|
|
19386
|
-
text={message.
|
|
20052
|
+
text={(message.parts ?? [])
|
|
20053
|
+
.map((part) => (part.type === "text" ? part.text : ""))
|
|
20054
|
+
.join("")}
|
|
19387
20055
|
isStreaming={message.status === "streaming"}
|
|
19388
20056
|
textColor={theme.text}
|
|
19389
20057
|
/>
|
|
@@ -19546,6 +20214,7 @@ const styles = StyleSheet.create({
|
|
|
19546
20214
|
});
|
|
19547
20215
|
{{else}}
|
|
19548
20216
|
import { useRef, useEffect, useState } from "react";
|
|
20217
|
+
import { Ionicons } from "@expo/vector-icons";
|
|
19549
20218
|
import {
|
|
19550
20219
|
View,
|
|
19551
20220
|
Text,
|
|
@@ -19558,8 +20227,6 @@ import {
|
|
|
19558
20227
|
} from "react-native";
|
|
19559
20228
|
import { useChat } from "@ai-sdk/react";
|
|
19560
20229
|
import { DefaultChatTransport } from "ai";
|
|
19561
|
-
import { fetch as expoFetch } from "expo/fetch";
|
|
19562
|
-
import { Ionicons } from "@expo/vector-icons";
|
|
19563
20230
|
import { Container } from "@/components/container";
|
|
19564
20231
|
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
19565
20232
|
import { NAV_THEME } from "@/lib/constants";
|
|
@@ -19582,7 +20249,6 @@ export default function AIScreen() {
|
|
|
19582
20249
|
const [input, setInput] = useState("");
|
|
19583
20250
|
const { messages, error, sendMessage } = useChat({
|
|
19584
20251
|
transport: new DefaultChatTransport({
|
|
19585
|
-
fetch: expoFetch as unknown as typeof globalThis.fetch,
|
|
19586
20252
|
api: generateAPIUrl("/ai"),
|
|
19587
20253
|
}),
|
|
19588
20254
|
onError: (error) => console.error(error, "AI Chat Error"),
|
|
@@ -19862,7 +20528,6 @@ import { Ionicons } from "@expo/vector-icons";
|
|
|
19862
20528
|
import {
|
|
19863
20529
|
useUIMessages,
|
|
19864
20530
|
useSmoothText,
|
|
19865
|
-
type UIMessage,
|
|
19866
20531
|
} from "@convex-dev/agent/react";
|
|
19867
20532
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
19868
20533
|
import { useMutation } from "convex/react";
|
|
@@ -19913,7 +20578,7 @@ export default function AIScreen() {
|
|
|
19913
20578
|
);
|
|
19914
20579
|
|
|
19915
20580
|
const hasStreamingMessage = messages?.some(
|
|
19916
|
-
(m
|
|
20581
|
+
(m) => m.status === "streaming",
|
|
19917
20582
|
);
|
|
19918
20583
|
|
|
19919
20584
|
useEffect(() => {
|
|
@@ -19969,7 +20634,7 @@ export default function AIScreen() {
|
|
|
19969
20634
|
</View>
|
|
19970
20635
|
) : (
|
|
19971
20636
|
<View style={styles.messagesWrapper}>
|
|
19972
|
-
{messages.map((message
|
|
20637
|
+
{messages.map((message) => (
|
|
19973
20638
|
<View
|
|
19974
20639
|
key={message.key}
|
|
19975
20640
|
style={[
|
|
@@ -19983,7 +20648,9 @@ export default function AIScreen() {
|
|
|
19983
20648
|
{message.role === "user" ? "You" : "AI Assistant"}
|
|
19984
20649
|
</Text>
|
|
19985
20650
|
<MessageContent
|
|
19986
|
-
text={message.
|
|
20651
|
+
text={(message.parts ?? [])
|
|
20652
|
+
.map((part) => (part.type === "text" ? part.text : ""))
|
|
20653
|
+
.join("")}
|
|
19987
20654
|
isStreaming={message.status === "streaming"}
|
|
19988
20655
|
style={styles.messageContent}
|
|
19989
20656
|
/>
|
|
@@ -20163,7 +20830,6 @@ import {
|
|
|
20163
20830
|
} from "react-native";
|
|
20164
20831
|
import { useChat } from "@ai-sdk/react";
|
|
20165
20832
|
import { DefaultChatTransport } from "ai";
|
|
20166
|
-
import { fetch as expoFetch } from "expo/fetch";
|
|
20167
20833
|
import { Ionicons } from "@expo/vector-icons";
|
|
20168
20834
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|
20169
20835
|
import { Container } from "@/components/container";
|
|
@@ -20185,7 +20851,6 @@ export default function AIScreen() {
|
|
|
20185
20851
|
const [input, setInput] = useState("");
|
|
20186
20852
|
const { messages, error, sendMessage } = useChat({
|
|
20187
20853
|
transport: new DefaultChatTransport({
|
|
20188
|
-
fetch: expoFetch as unknown as typeof globalThis.fetch,
|
|
20189
20854
|
api: generateAPIUrl("/ai"),
|
|
20190
20855
|
}),
|
|
20191
20856
|
onError: (error) => console.error(error, "AI Chat Error"),
|
|
@@ -20474,7 +21139,6 @@ import { Ionicons } from "@expo/vector-icons";
|
|
|
20474
21139
|
import {
|
|
20475
21140
|
useUIMessages,
|
|
20476
21141
|
useSmoothText,
|
|
20477
|
-
type UIMessage,
|
|
20478
21142
|
} from "@convex-dev/agent/react";
|
|
20479
21143
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
20480
21144
|
import { useMutation } from "convex/react";
|
|
@@ -20521,7 +21185,7 @@ export default function AIScreen() {
|
|
|
20521
21185
|
);
|
|
20522
21186
|
|
|
20523
21187
|
const hasStreamingMessage = messages?.some(
|
|
20524
|
-
(m
|
|
21188
|
+
(m) => m.status === "streaming",
|
|
20525
21189
|
);
|
|
20526
21190
|
|
|
20527
21191
|
useEffect(() => {
|
|
@@ -20576,7 +21240,7 @@ export default function AIScreen() {
|
|
|
20576
21240
|
</Surface>
|
|
20577
21241
|
) : (
|
|
20578
21242
|
<View className="gap-3">
|
|
20579
|
-
{messages.map((message
|
|
21243
|
+
{messages.map((message) => (
|
|
20580
21244
|
<Surface
|
|
20581
21245
|
key={message.key}
|
|
20582
21246
|
variant={message.role === "user" ? "tertiary" : "secondary"}
|
|
@@ -20586,7 +21250,9 @@ export default function AIScreen() {
|
|
|
20586
21250
|
{message.role === "user" ? "You" : "AI"}
|
|
20587
21251
|
</Text>
|
|
20588
21252
|
<MessageContent
|
|
20589
|
-
text={message.
|
|
21253
|
+
text={(message.parts ?? [])
|
|
21254
|
+
.map((part) => (part.type === "text" ? part.text : ""))
|
|
21255
|
+
.join("")}
|
|
20590
21256
|
isStreaming={message.status === "streaming"}
|
|
20591
21257
|
/>
|
|
20592
21258
|
</Surface>
|
|
@@ -20649,7 +21315,6 @@ import {
|
|
|
20649
21315
|
} from "react-native";
|
|
20650
21316
|
import { useChat } from "@ai-sdk/react";
|
|
20651
21317
|
import { DefaultChatTransport } from "ai";
|
|
20652
|
-
import { fetch as expoFetch } from "expo/fetch";
|
|
20653
21318
|
import { Ionicons } from "@expo/vector-icons";
|
|
20654
21319
|
import { Container } from "@/components/container";
|
|
20655
21320
|
import { Button, Separator, FieldError, Spinner, Surface, Input, TextField, useThemeColor } from "heroui-native";
|
|
@@ -20670,7 +21335,6 @@ export default function AIScreen() {
|
|
|
20670
21335
|
const [input, setInput] = useState("");
|
|
20671
21336
|
const { messages, error, sendMessage, status } = useChat({
|
|
20672
21337
|
transport: new DefaultChatTransport({
|
|
20673
|
-
fetch: expoFetch as unknown as typeof globalThis.fetch,
|
|
20674
21338
|
api: generateAPIUrl("/ai"),
|
|
20675
21339
|
}),
|
|
20676
21340
|
onError: (error) => console.error(error, "AI Chat Error"),
|
|
@@ -20994,7 +21658,6 @@ import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
|
20994
21658
|
import {
|
|
20995
21659
|
useUIMessages,
|
|
20996
21660
|
useSmoothText,
|
|
20997
|
-
type UIMessage,
|
|
20998
21661
|
} from "@convex-dev/agent/react";
|
|
20999
21662
|
import { useMutation } from "convex/react";
|
|
21000
21663
|
import { Send, Loader2 } from "lucide-react";
|
|
@@ -21053,7 +21716,7 @@ export default function AIPage() {
|
|
|
21053
21716
|
}, [messages]);
|
|
21054
21717
|
|
|
21055
21718
|
const hasStreamingMessage = messages?.some(
|
|
21056
|
-
(m
|
|
21719
|
+
(m) => m.status === "streaming",
|
|
21057
21720
|
);
|
|
21058
21721
|
|
|
21059
21722
|
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
|
@@ -21087,7 +21750,7 @@ export default function AIPage() {
|
|
|
21087
21750
|
Ask me anything to get started!
|
|
21088
21751
|
</div>
|
|
21089
21752
|
) : (
|
|
21090
|
-
messages.map((message
|
|
21753
|
+
messages.map((message) => (
|
|
21091
21754
|
<div
|
|
21092
21755
|
key={message.key}
|
|
21093
21756
|
className={\`p-3 rounded-lg \${
|
|
@@ -21100,7 +21763,9 @@ export default function AIPage() {
|
|
|
21100
21763
|
{message.role === "user" ? "You" : "AI Assistant"}
|
|
21101
21764
|
</p>
|
|
21102
21765
|
<MessageContent
|
|
21103
|
-
text={message.
|
|
21766
|
+
text={(message.parts ?? [])
|
|
21767
|
+
.map((part) => (part.type === "text" ? part.text : ""))
|
|
21768
|
+
.join("")}
|
|
21104
21769
|
isStreaming={message.status === "streaming"}
|
|
21105
21770
|
/>
|
|
21106
21771
|
</div>
|
|
@@ -21260,7 +21925,6 @@ import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
|
21260
21925
|
import {
|
|
21261
21926
|
useUIMessages,
|
|
21262
21927
|
useSmoothText,
|
|
21263
|
-
type UIMessage,
|
|
21264
21928
|
} from "@convex-dev/agent/react";
|
|
21265
21929
|
import { useMutation } from "convex/react";
|
|
21266
21930
|
import { Send, Loader2 } from "lucide-react";
|
|
@@ -21303,7 +21967,7 @@ const AI: React.FC = () => {
|
|
|
21303
21967
|
}, [messages]);
|
|
21304
21968
|
|
|
21305
21969
|
const hasStreamingMessage = messages?.some(
|
|
21306
|
-
(m
|
|
21970
|
+
(m) => m.status === "streaming",
|
|
21307
21971
|
);
|
|
21308
21972
|
|
|
21309
21973
|
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
|
@@ -21337,7 +22001,7 @@ const AI: React.FC = () => {
|
|
|
21337
22001
|
Ask me anything to get started!
|
|
21338
22002
|
</div>
|
|
21339
22003
|
) : (
|
|
21340
|
-
messages.map((message
|
|
22004
|
+
messages.map((message) => (
|
|
21341
22005
|
<div
|
|
21342
22006
|
key={message.key}
|
|
21343
22007
|
className={\`p-3 rounded-lg \${
|
|
@@ -21350,7 +22014,9 @@ const AI: React.FC = () => {
|
|
|
21350
22014
|
{message.role === "user" ? "You" : "AI Assistant"}
|
|
21351
22015
|
</p>
|
|
21352
22016
|
<MessageContent
|
|
21353
|
-
text={message.
|
|
22017
|
+
text={(message.parts ?? [])
|
|
22018
|
+
.map((part) => (part.type === "text" ? part.text : ""))
|
|
22019
|
+
.join("")}
|
|
21354
22020
|
isStreaming={message.status === "streaming"}
|
|
21355
22021
|
/>
|
|
21356
22022
|
</div>
|
|
@@ -21496,7 +22162,6 @@ import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
|
21496
22162
|
import {
|
|
21497
22163
|
useUIMessages,
|
|
21498
22164
|
useSmoothText,
|
|
21499
|
-
type UIMessage,
|
|
21500
22165
|
} from "@convex-dev/agent/react";
|
|
21501
22166
|
import { createFileRoute } from "@tanstack/react-router";
|
|
21502
22167
|
import { useMutation } from "convex/react";
|
|
@@ -21544,7 +22209,7 @@ function RouteComponent() {
|
|
|
21544
22209
|
}, [messages]);
|
|
21545
22210
|
|
|
21546
22211
|
const hasStreamingMessage = messages?.some(
|
|
21547
|
-
(m
|
|
22212
|
+
(m) => m.status === "streaming",
|
|
21548
22213
|
);
|
|
21549
22214
|
|
|
21550
22215
|
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
|
@@ -21578,7 +22243,7 @@ function RouteComponent() {
|
|
|
21578
22243
|
Ask me anything to get started!
|
|
21579
22244
|
</div>
|
|
21580
22245
|
) : (
|
|
21581
|
-
messages.map((message
|
|
22246
|
+
messages.map((message) => (
|
|
21582
22247
|
<div
|
|
21583
22248
|
key={message.key}
|
|
21584
22249
|
className={\`p-3 rounded-lg \${
|
|
@@ -21591,7 +22256,9 @@ function RouteComponent() {
|
|
|
21591
22256
|
{message.role === "user" ? "You" : "AI Assistant"}
|
|
21592
22257
|
</p>
|
|
21593
22258
|
<MessageContent
|
|
21594
|
-
text={message.
|
|
22259
|
+
text={(message.parts ?? [])
|
|
22260
|
+
.map((part) => (part.type === "text" ? part.text : ""))
|
|
22261
|
+
.join("")}
|
|
21595
22262
|
isStreaming={message.status === "streaming"}
|
|
21596
22263
|
/>
|
|
21597
22264
|
</div>
|
|
@@ -21739,7 +22406,6 @@ import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
|
21739
22406
|
import {
|
|
21740
22407
|
useUIMessages,
|
|
21741
22408
|
useSmoothText,
|
|
21742
|
-
type UIMessage,
|
|
21743
22409
|
} from "@convex-dev/agent/react";
|
|
21744
22410
|
import { createFileRoute } from "@tanstack/react-router";
|
|
21745
22411
|
import { useMutation } from "convex/react";
|
|
@@ -21787,7 +22453,7 @@ function RouteComponent() {
|
|
|
21787
22453
|
}, [messages]);
|
|
21788
22454
|
|
|
21789
22455
|
const hasStreamingMessage = messages?.some(
|
|
21790
|
-
(m
|
|
22456
|
+
(m) => m.status === "streaming",
|
|
21791
22457
|
);
|
|
21792
22458
|
|
|
21793
22459
|
const handleSubmit = async (e: FormEvent<HTMLFormElement>) => {
|
|
@@ -21821,7 +22487,7 @@ function RouteComponent() {
|
|
|
21821
22487
|
Ask me anything to get started!
|
|
21822
22488
|
</div>
|
|
21823
22489
|
) : (
|
|
21824
|
-
messages.map((message
|
|
22490
|
+
messages.map((message) => (
|
|
21825
22491
|
<div
|
|
21826
22492
|
key={message.key}
|
|
21827
22493
|
className={\`p-3 rounded-lg \${
|
|
@@ -21834,7 +22500,9 @@ function RouteComponent() {
|
|
|
21834
22500
|
{message.role === "user" ? "You" : "AI Assistant"}
|
|
21835
22501
|
</p>
|
|
21836
22502
|
<MessageContent
|
|
21837
|
-
text={message.
|
|
22503
|
+
text={(message.parts ?? [])
|
|
22504
|
+
.map((part) => (part.type === "text" ? part.text : ""))
|
|
22505
|
+
.join("")}
|
|
21838
22506
|
isStreaming={message.status === "streaming"}
|
|
21839
22507
|
/>
|
|
21840
22508
|
</div>
|
|
@@ -22149,7 +22817,7 @@ import { Ionicons } from "@expo/vector-icons";
|
|
|
22149
22817
|
{{#if (eq backend "convex")}}
|
|
22150
22818
|
import { useMutation, useQuery } from "convex/react";
|
|
22151
22819
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
22152
|
-
import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel";
|
|
22820
|
+
import type { Doc, Id } from "@{{projectName}}/backend/convex/_generated/dataModel";
|
|
22153
22821
|
{{else}}
|
|
22154
22822
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
22155
22823
|
{{/if}}
|
|
@@ -22199,7 +22867,7 @@ export default function TodosScreen() {
|
|
|
22199
22867
|
}
|
|
22200
22868
|
|
|
22201
22869
|
const isLoading = !todos;
|
|
22202
|
-
const completedCount = todos?.filter((t) => t.completed).length || 0;
|
|
22870
|
+
const completedCount = todos?.filter((t: Doc<"todos">) => t.completed).length || 0;
|
|
22203
22871
|
const totalCount = todos?.length || 0;
|
|
22204
22872
|
{{else}}
|
|
22205
22873
|
{{#if (eq api "orpc")}}
|
|
@@ -22408,7 +23076,7 @@ export default function TodosScreen() {
|
|
|
22408
23076
|
|
|
22409
23077
|
{todos && todos.length > 0 && (
|
|
22410
23078
|
<View style={styles.todosList}>
|
|
22411
|
-
{todos.map((todo) => (
|
|
23079
|
+
{todos.map((todo: Doc<"todos">) => (
|
|
22412
23080
|
<View
|
|
22413
23081
|
key={todo._id}
|
|
22414
23082
|
style={[
|
|
@@ -22598,9 +23266,10 @@ const styles = StyleSheet.create({
|
|
|
22598
23266
|
fontSize: 16,
|
|
22599
23267
|
},
|
|
22600
23268
|
addButton: {
|
|
22601
|
-
|
|
22602
|
-
|
|
23269
|
+
width: 48,
|
|
23270
|
+
height: 48,
|
|
22603
23271
|
alignItems: "center",
|
|
23272
|
+
justifyContent: "center",
|
|
22604
23273
|
},
|
|
22605
23274
|
centerContainer: {
|
|
22606
23275
|
alignItems: "center",
|
|
@@ -22638,23 +23307,24 @@ const styles = StyleSheet.create({
|
|
|
22638
23307
|
alignItems: "center",
|
|
22639
23308
|
gap: 12,
|
|
22640
23309
|
},
|
|
22641
|
-
checkbox: {
|
|
22642
|
-
width: 20,
|
|
22643
|
-
height: 20,
|
|
22644
|
-
borderWidth: 2,
|
|
22645
|
-
justifyContent: "center",
|
|
22646
|
-
alignItems: "center",
|
|
22647
|
-
},
|
|
22648
23310
|
todoTextContainer: {
|
|
22649
23311
|
flex: 1,
|
|
22650
23312
|
},
|
|
22651
23313
|
todoText: {
|
|
22652
23314
|
fontSize: 16,
|
|
22653
23315
|
},
|
|
23316
|
+
checkbox: {
|
|
23317
|
+
width: 24,
|
|
23318
|
+
height: 24,
|
|
23319
|
+
borderWidth: 2,
|
|
23320
|
+
alignItems: "center",
|
|
23321
|
+
justifyContent: "center",
|
|
23322
|
+
},
|
|
22654
23323
|
deleteButton: {
|
|
22655
|
-
padding:
|
|
23324
|
+
padding: 4,
|
|
22656
23325
|
},
|
|
22657
|
-
})
|
|
23326
|
+
});
|
|
23327
|
+
`],
|
|
22658
23328
|
["examples/todo/native/unistyles/app/(drawer)/todos.tsx.hbs", `import { useState } from "react";
|
|
22659
23329
|
import {
|
|
22660
23330
|
View,
|
|
@@ -22671,7 +23341,7 @@ import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|
|
22671
23341
|
{{#if (eq backend "convex")}}
|
|
22672
23342
|
import { useMutation, useQuery } from "convex/react";
|
|
22673
23343
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
22674
|
-
import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel";
|
|
23344
|
+
import type { Doc, Id } from "@{{projectName}}/backend/convex/_generated/dataModel";
|
|
22675
23345
|
{{else}}
|
|
22676
23346
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
22677
23347
|
{{/if}}
|
|
@@ -22839,7 +23509,7 @@ export default function TodosScreen() {
|
|
|
22839
23509
|
{todos && todos.length === 0 && !isLoading && (
|
|
22840
23510
|
<Text style={styles.emptyText}>No todos yet. Add one!</Text>
|
|
22841
23511
|
)}
|
|
22842
|
-
{todos?.map((todo) => (
|
|
23512
|
+
{todos?.map((todo: Doc<"todos">) => (
|
|
22843
23513
|
<View key={todo._id} style={styles.todoItem}>
|
|
22844
23514
|
<TouchableOpacity
|
|
22845
23515
|
onPress={() => handleToggleTodo(todo._id, todo.completed)}
|
|
@@ -23002,7 +23672,7 @@ import { Ionicons } from "@expo/vector-icons";
|
|
|
23002
23672
|
{{#if (eq backend "convex")}}
|
|
23003
23673
|
import { useMutation, useQuery } from "convex/react";
|
|
23004
23674
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
23005
|
-
import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel";
|
|
23675
|
+
import type { Doc, Id } from "@{{projectName}}/backend/convex/_generated/dataModel";
|
|
23006
23676
|
{{else}}
|
|
23007
23677
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
23008
23678
|
{{/if}}
|
|
@@ -23093,7 +23763,7 @@ export default function TodosScreen() {
|
|
|
23093
23763
|
};
|
|
23094
23764
|
|
|
23095
23765
|
const isLoading = !todos;
|
|
23096
|
-
const completedCount = todos?.filter((t) => t.completed).length || 0;
|
|
23766
|
+
const completedCount = todos?.filter((t: Doc<"todos">) => t.completed).length || 0;
|
|
23097
23767
|
const totalCount = todos?.length || 0;
|
|
23098
23768
|
{{else}}
|
|
23099
23769
|
const handleAddTodo = () => {
|
|
@@ -23205,7 +23875,7 @@ export default function TodosScreen() {
|
|
|
23205
23875
|
|
|
23206
23876
|
{todos && todos.length > 0 && (
|
|
23207
23877
|
<View className="gap-2">
|
|
23208
|
-
{todos.map((todo) => (
|
|
23878
|
+
{todos.map((todo: Doc<"todos">) => (
|
|
23209
23879
|
<Surface key={todo._id} variant="secondary" className="p-3 rounded-lg">
|
|
23210
23880
|
<View className="flex-row items-center gap-3">
|
|
23211
23881
|
<Checkbox
|
|
@@ -23277,7 +23947,8 @@ export default function TodosScreen() {
|
|
|
23277
23947
|
</ScrollView>
|
|
23278
23948
|
</Container>
|
|
23279
23949
|
);
|
|
23280
|
-
}
|
|
23950
|
+
}
|
|
23951
|
+
`],
|
|
23281
23952
|
["examples/todo/server/drizzle/base/src/routers/todo.ts.hbs", `{{#if (eq api "orpc")}}
|
|
23282
23953
|
import { eq } from "drizzle-orm";
|
|
23283
23954
|
import z from "zod";
|
|
@@ -23412,19 +24083,22 @@ export const todo = sqliteTable("todo", {
|
|
|
23412
24083
|
`],
|
|
23413
24084
|
["examples/todo/server/mongoose/base/src/routers/todo.ts.hbs", `{{#if (eq api "orpc")}}
|
|
23414
24085
|
import z from "zod";
|
|
24086
|
+
import "@{{projectName}}/db";
|
|
23415
24087
|
import { publicProcedure } from "../index";
|
|
23416
24088
|
import { Todo } from "@{{projectName}}/db/models/todo.model";
|
|
23417
24089
|
|
|
23418
24090
|
export const todoRouter = {
|
|
23419
24091
|
getAll: publicProcedure.handler(async () => {
|
|
23420
|
-
|
|
24092
|
+
const todos = await Todo.find().lean();
|
|
24093
|
+
return todos.map((todo) => ({ ...todo, id: todo.id }));
|
|
23421
24094
|
}),
|
|
23422
24095
|
|
|
23423
24096
|
create: publicProcedure
|
|
23424
24097
|
.input(z.object({ text: z.string().min(1) }))
|
|
23425
24098
|
.handler(async ({ input }) => {
|
|
23426
24099
|
const newTodo = await Todo.create({ text: input.text });
|
|
23427
|
-
|
|
24100
|
+
const todo = newTodo.toObject();
|
|
24101
|
+
return { ...todo, id: todo.id };
|
|
23428
24102
|
}),
|
|
23429
24103
|
|
|
23430
24104
|
toggle: publicProcedure
|
|
@@ -23446,19 +24120,22 @@ export const todoRouter = {
|
|
|
23446
24120
|
|
|
23447
24121
|
{{#if (eq api "trpc")}}
|
|
23448
24122
|
import z from "zod";
|
|
24123
|
+
import "@{{projectName}}/db";
|
|
23449
24124
|
import { router, publicProcedure } from "../index";
|
|
23450
24125
|
import { Todo } from "@{{projectName}}/db/models/todo.model";
|
|
23451
24126
|
|
|
23452
24127
|
export const todoRouter = router({
|
|
23453
24128
|
getAll: publicProcedure.query(async () => {
|
|
23454
|
-
|
|
24129
|
+
const todos = await Todo.find().lean();
|
|
24130
|
+
return todos.map((todo) => ({ ...todo, id: todo.id }));
|
|
23455
24131
|
}),
|
|
23456
24132
|
|
|
23457
24133
|
create: publicProcedure
|
|
23458
24134
|
.input(z.object({ text: z.string().min(1) }))
|
|
23459
24135
|
.mutation(async ({ input }) => {
|
|
23460
24136
|
const newTodo = await Todo.create({ text: input.text });
|
|
23461
|
-
|
|
24137
|
+
const todo = newTodo.toObject();
|
|
24138
|
+
return { ...todo, id: todo.id };
|
|
23462
24139
|
}),
|
|
23463
24140
|
|
|
23464
24141
|
toggle: publicProcedure
|
|
@@ -23483,8 +24160,9 @@ const { Schema, model } = mongoose;
|
|
|
23483
24160
|
|
|
23484
24161
|
const todoSchema = new Schema({
|
|
23485
24162
|
id: {
|
|
23486
|
-
type:
|
|
23487
|
-
|
|
24163
|
+
type: String,
|
|
24164
|
+
required: true,
|
|
24165
|
+
default: () => new mongoose.Types.ObjectId().toString(),
|
|
23488
24166
|
},
|
|
23489
24167
|
text: {
|
|
23490
24168
|
type: String,
|
|
@@ -23495,7 +24173,8 @@ const todoSchema = new Schema({
|
|
|
23495
24173
|
default: false,
|
|
23496
24174
|
},
|
|
23497
24175
|
}, {
|
|
23498
|
-
collection: 'todo'
|
|
24176
|
+
collection: 'todo',
|
|
24177
|
+
id: false,
|
|
23499
24178
|
});
|
|
23500
24179
|
|
|
23501
24180
|
const Todo = model('Todo', todoSchema);
|
|
@@ -24103,6 +24782,9 @@ import { trpc } from "@/utils/trpc";
|
|
|
24103
24782
|
{{/if}}
|
|
24104
24783
|
{{/if}}
|
|
24105
24784
|
|
|
24785
|
+
{{#unless (eq backend "convex")}}
|
|
24786
|
+
type TodoId = {{#if (or (eq orm "mongoose") (eq database "mongodb"))}}string{{else}}number{{/if}};
|
|
24787
|
+
{{/unless}}
|
|
24106
24788
|
|
|
24107
24789
|
export default function TodosPage() {
|
|
24108
24790
|
const [newTodoText, setNewTodoText] = useState("");
|
|
@@ -24179,11 +24861,11 @@ export default function TodosPage() {
|
|
|
24179
24861
|
}
|
|
24180
24862
|
};
|
|
24181
24863
|
|
|
24182
|
-
const handleToggleTodo = (id:
|
|
24864
|
+
const handleToggleTodo = (id: TodoId, completed: boolean) => {
|
|
24183
24865
|
toggleMutation.mutate({ id, completed: !completed });
|
|
24184
24866
|
};
|
|
24185
24867
|
|
|
24186
|
-
const handleDeleteTodo = (id:
|
|
24868
|
+
const handleDeleteTodo = (id: TodoId) => {
|
|
24187
24869
|
deleteMutation.mutate({ id });
|
|
24188
24870
|
};
|
|
24189
24871
|
{{/if}}
|
|
@@ -24347,6 +25029,10 @@ import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel";
|
|
|
24347
25029
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
24348
25030
|
{{/if}}
|
|
24349
25031
|
|
|
25032
|
+
{{#unless (eq backend "convex")}}
|
|
25033
|
+
type TodoId = {{#if (or (eq orm "mongoose") (eq database "mongodb"))}}string{{else}}number{{/if}};
|
|
25034
|
+
{{/unless}}
|
|
25035
|
+
|
|
24350
25036
|
export default function Todos() {
|
|
24351
25037
|
const [newTodoText, setNewTodoText] = useState("");
|
|
24352
25038
|
|
|
@@ -24422,11 +25108,11 @@ export default function Todos() {
|
|
|
24422
25108
|
}
|
|
24423
25109
|
};
|
|
24424
25110
|
|
|
24425
|
-
const handleToggleTodo = (id:
|
|
25111
|
+
const handleToggleTodo = (id: TodoId, completed: boolean) => {
|
|
24426
25112
|
toggleMutation.mutate({ id, completed: !completed });
|
|
24427
25113
|
};
|
|
24428
25114
|
|
|
24429
|
-
const handleDeleteTodo = (id:
|
|
25115
|
+
const handleDeleteTodo = (id: TodoId) => {
|
|
24430
25116
|
deleteMutation.mutate({ id });
|
|
24431
25117
|
};
|
|
24432
25118
|
{{/if}}
|
|
@@ -24591,6 +25277,10 @@ import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel";
|
|
|
24591
25277
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
24592
25278
|
{{/if}}
|
|
24593
25279
|
|
|
25280
|
+
{{#unless (eq backend "convex")}}
|
|
25281
|
+
type TodoId = {{#if (or (eq orm "mongoose") (eq database "mongodb"))}}string{{else}}number{{/if}};
|
|
25282
|
+
{{/unless}}
|
|
25283
|
+
|
|
24594
25284
|
export const Route = createFileRoute("/todos")({
|
|
24595
25285
|
component: TodosRoute,
|
|
24596
25286
|
});
|
|
@@ -24670,11 +25360,11 @@ function TodosRoute() {
|
|
|
24670
25360
|
}
|
|
24671
25361
|
};
|
|
24672
25362
|
|
|
24673
|
-
const handleToggleTodo = (id:
|
|
25363
|
+
const handleToggleTodo = (id: TodoId, completed: boolean) => {
|
|
24674
25364
|
toggleMutation.mutate({ id, completed: !completed });
|
|
24675
25365
|
};
|
|
24676
25366
|
|
|
24677
|
-
const handleDeleteTodo = (id:
|
|
25367
|
+
const handleDeleteTodo = (id: TodoId) => {
|
|
24678
25368
|
deleteMutation.mutate({ id });
|
|
24679
25369
|
};
|
|
24680
25370
|
{{/if}}
|
|
@@ -24845,6 +25535,10 @@ import { orpc } from "@/utils/orpc";
|
|
|
24845
25535
|
import { useMutation, useQuery } from "@tanstack/react-query";
|
|
24846
25536
|
{{/if}}
|
|
24847
25537
|
|
|
25538
|
+
{{#unless (eq backend "convex")}}
|
|
25539
|
+
type TodoId = {{#if (or (eq orm "mongoose") (eq database "mongodb"))}}string{{else}}number{{/if}};
|
|
25540
|
+
{{/unless}}
|
|
25541
|
+
|
|
24848
25542
|
export const Route = createFileRoute("/todos")({
|
|
24849
25543
|
component: TodosRoute,
|
|
24850
25544
|
});
|
|
@@ -24946,11 +25640,11 @@ function TodosRoute() {
|
|
|
24946
25640
|
}
|
|
24947
25641
|
};
|
|
24948
25642
|
|
|
24949
|
-
const handleToggleTodo = (id:
|
|
25643
|
+
const handleToggleTodo = (id: TodoId, completed: boolean) => {
|
|
24950
25644
|
toggleMutation.mutate({ id, completed: !completed });
|
|
24951
25645
|
};
|
|
24952
25646
|
|
|
24953
|
-
const handleDeleteTodo = (id:
|
|
25647
|
+
const handleDeleteTodo = (id: TodoId) => {
|
|
24954
25648
|
deleteMutation.mutate({ id });
|
|
24955
25649
|
};
|
|
24956
25650
|
{{/if}}
|
|
@@ -25541,11 +26235,15 @@ shamefully-hoist=true
|
|
|
25541
26235
|
strict-peer-dependencies=false
|
|
25542
26236
|
{{/if}}`],
|
|
25543
26237
|
["extras/bunfig.toml.hbs", `[install]
|
|
25544
|
-
{{#if (
|
|
25545
|
-
linker = "hoisted" #
|
|
26238
|
+
{{#if (includes frontend "nuxt")}}
|
|
26239
|
+
linker = "hoisted" # Nuxt needs hoisting for its dependency resolver
|
|
25546
26240
|
{{else}}
|
|
25547
26241
|
linker = "isolated"
|
|
25548
|
-
{{
|
|
26242
|
+
{{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
|
|
26243
|
+
peer = false # Expo native projects declare SDK peers explicitly; this keeps Bun isolated installs deduped for native modules
|
|
26244
|
+
{{/if}}
|
|
26245
|
+
{{/if}}
|
|
26246
|
+
`],
|
|
25549
26247
|
["extras/env.d.ts.hbs", `{{#if (eq serverDeploy "cloudflare")}}
|
|
25550
26248
|
import { type server } from "@{{projectName}}/infra/alchemy.run";
|
|
25551
26249
|
{{else}}
|
|
@@ -25567,9 +26265,40 @@ declare module "cloudflare:workers" {
|
|
|
25567
26265
|
}
|
|
25568
26266
|
}
|
|
25569
26267
|
`],
|
|
25570
|
-
["extras/pnpm-workspace.yaml", `packages:
|
|
26268
|
+
["extras/pnpm-workspace.yaml.hbs", `packages:
|
|
25571
26269
|
- "apps/*"
|
|
25572
26270
|
- "packages/*"
|
|
26271
|
+
{{#if (or (eq runtime "node") (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (eq orm "prisma") (includes addons "lefthook") (includes addons "nx") (includes addons "pwa") (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "tanstack-start") (includes frontend "next") (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
|
|
26272
|
+
|
|
26273
|
+
# pnpm 11 blocks dependency lifecycle scripts unless they are approved here.
|
|
26274
|
+
# Entries are scoped to packages this generated stack can pull in.
|
|
26275
|
+
allowBuilds:
|
|
26276
|
+
{{#if (or (eq runtime "node") (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (includes frontend "tanstack-start"))}}
|
|
26277
|
+
esbuild: true
|
|
26278
|
+
{{/if}}
|
|
26279
|
+
{{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "tanstack-start") (includes frontend "next"))}}
|
|
26280
|
+
msw: true
|
|
26281
|
+
{{/if}}
|
|
26282
|
+
{{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}}
|
|
26283
|
+
msgpackr-extract: true
|
|
26284
|
+
{{/if}}
|
|
26285
|
+
{{#if (or (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare") (includes addons "pwa"))}}
|
|
26286
|
+
sharp: true
|
|
26287
|
+
{{/if}}
|
|
26288
|
+
{{#if (or (eq webDeploy "cloudflare") (eq serverDeploy "cloudflare"))}}
|
|
26289
|
+
workerd: true
|
|
26290
|
+
{{/if}}
|
|
26291
|
+
{{#if (eq orm "prisma")}}
|
|
26292
|
+
"@prisma/engines": true
|
|
26293
|
+
prisma: true
|
|
26294
|
+
{{/if}}
|
|
26295
|
+
{{#if (includes addons "lefthook")}}
|
|
26296
|
+
lefthook: true
|
|
26297
|
+
{{/if}}
|
|
26298
|
+
{{#if (includes addons "nx")}}
|
|
26299
|
+
nx: true
|
|
26300
|
+
{{/if}}
|
|
26301
|
+
{{/if}}
|
|
25573
26302
|
`],
|
|
25574
26303
|
["frontend/astro/_gitignore", `# build output
|
|
25575
26304
|
dist/
|
|
@@ -25938,12 +26667,7 @@ import { env } from "@{{projectName}}/env/native";
|
|
|
25938
26667
|
{{/if}}
|
|
25939
26668
|
|
|
25940
26669
|
import { Stack } from "expo-router";
|
|
25941
|
-
import {
|
|
25942
|
-
DarkTheme,
|
|
25943
|
-
DefaultTheme,
|
|
25944
|
-
type Theme,
|
|
25945
|
-
ThemeProvider,
|
|
25946
|
-
} from "@react-navigation/native";
|
|
26670
|
+
import { DarkTheme, DefaultTheme, ThemeProvider } from "expo-router/react-navigation";
|
|
25947
26671
|
import { StatusBar } from "expo-status-bar";
|
|
25948
26672
|
import { GestureHandlerRootView } from "react-native-gesture-handler";
|
|
25949
26673
|
{{#if (eq api "trpc")}}
|
|
@@ -25956,11 +26680,11 @@ import { NAV_THEME } from "@/lib/constants";
|
|
|
25956
26680
|
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
25957
26681
|
import { StyleSheet } from "react-native";
|
|
25958
26682
|
|
|
25959
|
-
const LIGHT_THEME
|
|
26683
|
+
const LIGHT_THEME = {
|
|
25960
26684
|
...DefaultTheme,
|
|
25961
26685
|
colors: NAV_THEME.light,
|
|
25962
26686
|
};
|
|
25963
|
-
const DARK_THEME
|
|
26687
|
+
const DARK_THEME = {
|
|
25964
26688
|
...DarkTheme,
|
|
25965
26689
|
colors: NAV_THEME.dark,
|
|
25966
26690
|
};
|
|
@@ -25971,9 +26695,6 @@ export const unstable_settings = {
|
|
|
25971
26695
|
|
|
25972
26696
|
{{#if (eq backend "convex")}}
|
|
25973
26697
|
const convex = new ConvexReactClient(env.EXPO_PUBLIC_CONVEX_URL, {
|
|
25974
|
-
{{#if (eq auth "better-auth")}}
|
|
25975
|
-
expectAuth: true,
|
|
25976
|
-
{{/if}}
|
|
25977
26698
|
unsavedChangesWarning: false,
|
|
25978
26699
|
});
|
|
25979
26700
|
{{/if}}
|
|
@@ -26240,7 +26961,8 @@ export default function TabLayout() {
|
|
|
26240
26961
|
|
|
26241
26962
|
`],
|
|
26242
26963
|
["frontend/native/bare/app/(drawer)/(tabs)/index.tsx.hbs", `import { Container } from "@/components/container";
|
|
26243
|
-
import {
|
|
26964
|
+
import { Column, Host, Text as ExpoUIText } from "@expo/ui";
|
|
26965
|
+
import { ScrollView, View, StyleSheet } from "react-native";
|
|
26244
26966
|
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
26245
26967
|
import { NAV_THEME } from "@/lib/constants";
|
|
26246
26968
|
|
|
@@ -26252,12 +26974,21 @@ export default function TabOne() {
|
|
|
26252
26974
|
<Container>
|
|
26253
26975
|
<ScrollView style={styles.scrollView}>
|
|
26254
26976
|
<View style={styles.content}>
|
|
26255
|
-
<
|
|
26256
|
-
|
|
26257
|
-
|
|
26258
|
-
|
|
26259
|
-
|
|
26260
|
-
|
|
26977
|
+
<Host matchContents=\\{{ vertical: true }}>
|
|
26978
|
+
<Column spacing={8}>
|
|
26979
|
+
<ExpoUIText
|
|
26980
|
+
textStyle=\\{{ color: theme.text, fontSize: 24, fontWeight: "bold" }}
|
|
26981
|
+
>
|
|
26982
|
+
Tab One
|
|
26983
|
+
</ExpoUIText>
|
|
26984
|
+
<ExpoUIText
|
|
26985
|
+
textStyle=\\{{ color: theme.text, fontSize: 16 }}
|
|
26986
|
+
style=\\{{ opacity: 0.7 }}
|
|
26987
|
+
>
|
|
26988
|
+
Explore the first section of your app
|
|
26989
|
+
</ExpoUIText>
|
|
26990
|
+
</Column>
|
|
26991
|
+
</Host>
|
|
26261
26992
|
</View>
|
|
26262
26993
|
</ScrollView>
|
|
26263
26994
|
</Container>
|
|
@@ -26272,19 +27003,11 @@ const styles = StyleSheet.create({
|
|
|
26272
27003
|
content: {
|
|
26273
27004
|
paddingVertical: 16,
|
|
26274
27005
|
},
|
|
26275
|
-
title: {
|
|
26276
|
-
fontSize: 24,
|
|
26277
|
-
fontWeight: "bold",
|
|
26278
|
-
marginBottom: 8,
|
|
26279
|
-
},
|
|
26280
|
-
subtitle: {
|
|
26281
|
-
fontSize: 16,
|
|
26282
|
-
},
|
|
26283
27006
|
});
|
|
26284
|
-
|
|
26285
27007
|
`],
|
|
26286
27008
|
["frontend/native/bare/app/(drawer)/(tabs)/two.tsx.hbs", `import { Container } from "@/components/container";
|
|
26287
|
-
import {
|
|
27009
|
+
import { Column, Host, Text as ExpoUIText } from "@expo/ui";
|
|
27010
|
+
import { ScrollView, View, StyleSheet } from "react-native";
|
|
26288
27011
|
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
26289
27012
|
import { NAV_THEME } from "@/lib/constants";
|
|
26290
27013
|
|
|
@@ -26296,12 +27019,21 @@ export default function TabTwo() {
|
|
|
26296
27019
|
<Container>
|
|
26297
27020
|
<ScrollView style={styles.scrollView}>
|
|
26298
27021
|
<View style={styles.content}>
|
|
26299
|
-
<
|
|
26300
|
-
|
|
26301
|
-
|
|
26302
|
-
|
|
26303
|
-
|
|
26304
|
-
|
|
27022
|
+
<Host matchContents=\\{{ vertical: true }}>
|
|
27023
|
+
<Column spacing={8}>
|
|
27024
|
+
<ExpoUIText
|
|
27025
|
+
textStyle=\\{{ color: theme.text, fontSize: 24, fontWeight: "bold" }}
|
|
27026
|
+
>
|
|
27027
|
+
Tab Two
|
|
27028
|
+
</ExpoUIText>
|
|
27029
|
+
<ExpoUIText
|
|
27030
|
+
textStyle=\\{{ color: theme.text, fontSize: 16 }}
|
|
27031
|
+
style=\\{{ opacity: 0.7 }}
|
|
27032
|
+
>
|
|
27033
|
+
Discover more features and content
|
|
27034
|
+
</ExpoUIText>
|
|
27035
|
+
</Column>
|
|
27036
|
+
</Host>
|
|
26305
27037
|
</View>
|
|
26306
27038
|
</ScrollView>
|
|
26307
27039
|
</Container>
|
|
@@ -26316,18 +27048,15 @@ const styles = StyleSheet.create({
|
|
|
26316
27048
|
content: {
|
|
26317
27049
|
paddingVertical: 16,
|
|
26318
27050
|
},
|
|
26319
|
-
title: {
|
|
26320
|
-
fontSize: 24,
|
|
26321
|
-
fontWeight: "bold",
|
|
26322
|
-
marginBottom: 8,
|
|
26323
|
-
},
|
|
26324
|
-
subtitle: {
|
|
26325
|
-
fontSize: 16,
|
|
26326
|
-
},
|
|
26327
27051
|
});
|
|
26328
|
-
|
|
26329
27052
|
`],
|
|
26330
|
-
["frontend/native/bare/app/(drawer)/index.tsx.hbs", `import {
|
|
27053
|
+
["frontend/native/bare/app/(drawer)/index.tsx.hbs", `import { {{#if (or (eq auth "clerk") (eq auth "better-auth"))}}Button, {{/if}}Column, Host, Text as ExpoUIText } from "@expo/ui";
|
|
27054
|
+
import { View, ScrollView, StyleSheet{{#if (and (eq backend "convex") (eq auth "better-auth") (eq payments "polar"))}}, Alert{{/if}} } from "react-native";
|
|
27055
|
+
{{#if (and (eq backend "convex") (eq auth "better-auth") (eq payments "polar"))}}
|
|
27056
|
+
import * as Linking from "expo-linking";
|
|
27057
|
+
import * as WebBrowser from "expo-web-browser";
|
|
27058
|
+
import { env } from "@{{projectName}}/env/native";
|
|
27059
|
+
{{/if}}
|
|
26331
27060
|
import { Container } from "@/components/container";
|
|
26332
27061
|
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
26333
27062
|
import { NAV_THEME } from "@/lib/constants";
|
|
@@ -26340,17 +27069,17 @@ import { useQuery } from "@tanstack/react-query";
|
|
|
26340
27069
|
import { trpc } from "@/utils/trpc";
|
|
26341
27070
|
{{/if}}
|
|
26342
27071
|
{{#if (and (eq backend "convex") (eq auth "clerk"))}}
|
|
26343
|
-
import {
|
|
27072
|
+
import { router } from "expo-router";
|
|
26344
27073
|
import { Authenticated, AuthLoading, Unauthenticated, useQuery } from "convex/react";
|
|
26345
27074
|
import { api } from "@{{ projectName }}/backend/convex/_generated/api";
|
|
26346
27075
|
import { useUser } from "@clerk/expo";
|
|
26347
27076
|
import { SignOutButton } from "@/components/sign-out-button";
|
|
26348
27077
|
{{else if (and (ne backend "convex") (eq auth "clerk"))}}
|
|
26349
|
-
import {
|
|
27078
|
+
import { router } from "expo-router";
|
|
26350
27079
|
import { useAuth, useUser } from "@clerk/expo";
|
|
26351
27080
|
import { SignOutButton } from "@/components/sign-out-button";
|
|
26352
27081
|
{{else if (and (eq backend "convex") (eq auth "better-auth"))}}
|
|
26353
|
-
import { useConvexAuth, useQuery } from "convex/react";
|
|
27082
|
+
import { {{#if (eq payments "polar")}}useAction, {{/if}}useConvexAuth, useQuery } from "convex/react";
|
|
26354
27083
|
import { api } from "@{{ projectName }}/backend/convex/_generated/api";
|
|
26355
27084
|
import { authClient } from "@/lib/auth-client";
|
|
26356
27085
|
import { SignIn } from "@/components/sign-in";
|
|
@@ -26380,17 +27109,72 @@ const { user } = useUser();
|
|
|
26380
27109
|
const healthCheck = useQuery(api.healthCheck.get);
|
|
26381
27110
|
const { isAuthenticated } = useConvexAuth();
|
|
26382
27111
|
const user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : "skip");
|
|
27112
|
+
{{#if (eq payments "polar")}}
|
|
27113
|
+
const products = useQuery(api.polar.listAllProducts);
|
|
27114
|
+
const subscription = useQuery(api.polar.getCurrentSubscription);
|
|
27115
|
+
const generateCheckoutLink = useAction(api.polar.generateCheckoutLink);
|
|
27116
|
+
const generateCustomerPortalUrl = useAction(api.polar.generateCustomerPortalUrl);
|
|
27117
|
+
const recurringProduct = products?.find((product) => product.isRecurring);
|
|
27118
|
+
|
|
27119
|
+
const openPolarLink = async (url: string, returnUrl: string) => {
|
|
27120
|
+
await WebBrowser.openAuthSessionAsync(url, returnUrl);
|
|
27121
|
+
};
|
|
27122
|
+
|
|
27123
|
+
const getPolarReturnUrl = (returnUrl: string) => {
|
|
27124
|
+
const url = new URL("/polar/success", env.EXPO_PUBLIC_CONVEX_SITE_URL);
|
|
27125
|
+
url.searchParams.set("returnUrl", returnUrl);
|
|
27126
|
+
return url.toString();
|
|
27127
|
+
};
|
|
27128
|
+
|
|
27129
|
+
const handlePolarCheckout = async () => {
|
|
27130
|
+
try {
|
|
27131
|
+
if (!recurringProduct) {
|
|
27132
|
+
Alert.alert("Checkout unavailable", "No recurring Polar product is available yet.");
|
|
27133
|
+
return;
|
|
27134
|
+
}
|
|
27135
|
+
|
|
27136
|
+
const returnUrl = Linking.createURL("/");
|
|
27137
|
+
const polarReturnUrl = getPolarReturnUrl(returnUrl);
|
|
27138
|
+
const { url } = await generateCheckoutLink({
|
|
27139
|
+
productIds: [recurringProduct.id],
|
|
27140
|
+
origin: env.EXPO_PUBLIC_CONVEX_SITE_URL,
|
|
27141
|
+
successUrl: polarReturnUrl,
|
|
27142
|
+
});
|
|
27143
|
+
|
|
27144
|
+
await openPolarLink(url, returnUrl);
|
|
27145
|
+
} catch {
|
|
27146
|
+
Alert.alert("Checkout failed", "Unable to open Polar checkout. Please try again.");
|
|
27147
|
+
}
|
|
27148
|
+
};
|
|
27149
|
+
|
|
27150
|
+
const handlePolarPortal = async () => {
|
|
27151
|
+
try {
|
|
27152
|
+
const returnUrl = Linking.createURL("/");
|
|
27153
|
+
const { url } = await generateCustomerPortalUrl({
|
|
27154
|
+
returnUrl: getPolarReturnUrl(returnUrl),
|
|
27155
|
+
});
|
|
27156
|
+
|
|
27157
|
+
await openPolarLink(url, returnUrl);
|
|
27158
|
+
} catch {
|
|
27159
|
+
Alert.alert("Portal unavailable", "Unable to open the customer portal. Please try again.");
|
|
27160
|
+
}
|
|
27161
|
+
};
|
|
27162
|
+
{{/if}}
|
|
26383
27163
|
{{else if (eq backend "convex")}}
|
|
26384
27164
|
const healthCheck = useQuery(api.healthCheck.get);
|
|
26385
27165
|
{{/if}}
|
|
26386
27166
|
|
|
26387
27167
|
return (
|
|
26388
27168
|
<Container>
|
|
26389
|
-
<ScrollView style={styles.scrollView}>
|
|
27169
|
+
<ScrollView style={styles.scrollView} contentInsetAdjustmentBehavior="never">
|
|
26390
27170
|
<View style={styles.content}>
|
|
26391
|
-
<
|
|
26392
|
-
|
|
26393
|
-
|
|
27171
|
+
<Host style={styles.titleHost}>
|
|
27172
|
+
<ExpoUIText
|
|
27173
|
+
textStyle=\\{{ color: theme.text, fontSize: 24, fontWeight: "bold", textAlign: "center" }}
|
|
27174
|
+
>
|
|
27175
|
+
BETTER T STACK
|
|
27176
|
+
</ExpoUIText>
|
|
27177
|
+
</Host>
|
|
26394
27178
|
|
|
26395
27179
|
{{#unless (and (eq backend "convex") (eq auth "better-auth"))}}
|
|
26396
27180
|
<View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
@@ -26398,16 +27182,25 @@ return (
|
|
|
26398
27182
|
<View style={styles.statusRow}>
|
|
26399
27183
|
<View style={[styles.statusIndicator, { backgroundColor: healthCheck ? "#10b981" : "#f59e0b" }]} />
|
|
26400
27184
|
<View style={styles.statusContent}>
|
|
26401
|
-
<
|
|
26402
|
-
|
|
26403
|
-
|
|
26404
|
-
|
|
26405
|
-
|
|
26406
|
-
|
|
26407
|
-
|
|
26408
|
-
|
|
26409
|
-
|
|
26410
|
-
|
|
27185
|
+
<Host matchContents=\\{{ vertical: true }}>
|
|
27186
|
+
<Column spacing={4}>
|
|
27187
|
+
<ExpoUIText
|
|
27188
|
+
textStyle=\\{{ color: theme.text, fontSize: 14, fontWeight: "bold" }}
|
|
27189
|
+
>
|
|
27190
|
+
Convex
|
|
27191
|
+
</ExpoUIText>
|
|
27192
|
+
<ExpoUIText
|
|
27193
|
+
textStyle=\\{{ color: theme.text, fontSize: 12 }}
|
|
27194
|
+
style=\\{{ opacity: 0.7 }}
|
|
27195
|
+
>
|
|
27196
|
+
{healthCheck === undefined
|
|
27197
|
+
? "Checking..."
|
|
27198
|
+
: healthCheck === "OK"
|
|
27199
|
+
? "Connected to API"
|
|
27200
|
+
: "API Disconnected"}
|
|
27201
|
+
</ExpoUIText>
|
|
27202
|
+
</Column>
|
|
27203
|
+
</Host>
|
|
26411
27204
|
</View>
|
|
26412
27205
|
</View>
|
|
26413
27206
|
{{else}}
|
|
@@ -26415,16 +27208,25 @@ return (
|
|
|
26415
27208
|
<View style={styles.statusRow}>
|
|
26416
27209
|
<View style={[styles.statusIndicator, { backgroundColor: healthCheck.data ? "#10b981" : "#f59e0b" }]} />
|
|
26417
27210
|
<View style={styles.statusContent}>
|
|
26418
|
-
<
|
|
26419
|
-
|
|
26420
|
-
|
|
26421
|
-
|
|
26422
|
-
|
|
26423
|
-
|
|
26424
|
-
|
|
26425
|
-
|
|
26426
|
-
|
|
26427
|
-
|
|
27211
|
+
<Host matchContents=\\{{ vertical: true }}>
|
|
27212
|
+
<Column spacing={4}>
|
|
27213
|
+
<ExpoUIText
|
|
27214
|
+
textStyle=\\{{ color: theme.text, fontSize: 14, fontWeight: "bold" }}
|
|
27215
|
+
>
|
|
27216
|
+
{{#if (eq api "orpc")}}ORPC{{else}}TRPC{{/if}}
|
|
27217
|
+
</ExpoUIText>
|
|
27218
|
+
<ExpoUIText
|
|
27219
|
+
textStyle=\\{{ color: theme.text, fontSize: 12 }}
|
|
27220
|
+
style=\\{{ opacity: 0.7 }}
|
|
27221
|
+
>
|
|
27222
|
+
{healthCheck.isLoading
|
|
27223
|
+
? "Checking connection..."
|
|
27224
|
+
: healthCheck.data
|
|
27225
|
+
? "All systems operational"
|
|
27226
|
+
: "Service unavailable"}
|
|
27227
|
+
</ExpoUIText>
|
|
27228
|
+
</Column>
|
|
27229
|
+
</Host>
|
|
26428
27230
|
</View>
|
|
26429
27231
|
</View>
|
|
26430
27232
|
{{/unless}}
|
|
@@ -26434,85 +27236,154 @@ return (
|
|
|
26434
27236
|
|
|
26435
27237
|
{{#if (and (eq backend "convex") (eq auth "clerk"))}}
|
|
26436
27238
|
<Authenticated>
|
|
26437
|
-
<
|
|
26438
|
-
|
|
27239
|
+
<Host style={styles.authHost} matchContents=\\{{ vertical: true }}>
|
|
27240
|
+
<Column spacing={6}>
|
|
27241
|
+
<ExpoUIText textStyle=\\{{ color: theme.text, fontSize: 14 }}>
|
|
27242
|
+
{\`Hello \${user?.emailAddresses[0].emailAddress ?? ""}\`}
|
|
27243
|
+
</ExpoUIText>
|
|
27244
|
+
<ExpoUIText textStyle=\\{{ color: theme.text, fontSize: 14 }}>
|
|
27245
|
+
{\`Private Data: \${privateData?.message ?? ""}\`}
|
|
27246
|
+
</ExpoUIText>
|
|
27247
|
+
</Column>
|
|
27248
|
+
</Host>
|
|
26439
27249
|
<SignOutButton />
|
|
26440
27250
|
</Authenticated>
|
|
26441
27251
|
<Unauthenticated>
|
|
26442
|
-
<
|
|
26443
|
-
|
|
26444
|
-
|
|
26445
|
-
|
|
26446
|
-
|
|
26447
|
-
|
|
27252
|
+
<Host style={styles.authActionsHost} matchContents=\\{{ vertical: true }}>
|
|
27253
|
+
<Column spacing={8}>
|
|
27254
|
+
<Button
|
|
27255
|
+
label="Sign in"
|
|
27256
|
+
variant="outlined"
|
|
27257
|
+
onPress={() => router.push("/(auth)/sign-in")}
|
|
27258
|
+
/>
|
|
27259
|
+
<Button
|
|
27260
|
+
label="Sign up"
|
|
27261
|
+
onPress={() => router.push("/(auth)/sign-up")}
|
|
27262
|
+
/>
|
|
27263
|
+
</Column>
|
|
27264
|
+
</Host>
|
|
26448
27265
|
</Unauthenticated>
|
|
26449
27266
|
<AuthLoading>
|
|
26450
|
-
<
|
|
27267
|
+
<Host matchContents=\\{{ vertical: true }}>
|
|
27268
|
+
<ExpoUIText textStyle=\\{{ color: theme.text, fontSize: 14 }}>
|
|
27269
|
+
Loading...
|
|
27270
|
+
</ExpoUIText>
|
|
27271
|
+
</Host>
|
|
26451
27272
|
</AuthLoading>
|
|
26452
27273
|
{{/if}}
|
|
26453
27274
|
|
|
26454
27275
|
{{#if (and (ne backend "convex") (eq auth "clerk"))}}
|
|
26455
27276
|
{!isLoaded ? (
|
|
26456
|
-
<
|
|
27277
|
+
<Host matchContents=\\{{ vertical: true }}>
|
|
27278
|
+
<ExpoUIText textStyle=\\{{ color: theme.text, fontSize: 14 }}>
|
|
27279
|
+
Loading...
|
|
27280
|
+
</ExpoUIText>
|
|
27281
|
+
</Host>
|
|
26457
27282
|
) : isSignedIn ? (
|
|
26458
27283
|
<View style={[styles.userCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
26459
|
-
<
|
|
26460
|
-
<
|
|
26461
|
-
|
|
26462
|
-
|
|
26463
|
-
|
|
26464
|
-
|
|
26465
|
-
|
|
26466
|
-
|
|
27284
|
+
<Host style={styles.userHeader} matchContents=\\{{ vertical: true }}>
|
|
27285
|
+
<Column spacing={8}>
|
|
27286
|
+
<ExpoUIText textStyle=\\{{ color: theme.text, fontSize: 16 }}>
|
|
27287
|
+
{\`Welcome, \${user?.fullName ?? user?.firstName ?? "there"}\`}
|
|
27288
|
+
</ExpoUIText>
|
|
27289
|
+
<ExpoUIText
|
|
27290
|
+
textStyle=\\{{ color: theme.text, fontSize: 14 }}
|
|
27291
|
+
style=\\{{ opacity: 0.7 }}
|
|
27292
|
+
>
|
|
27293
|
+
{user?.emailAddresses[0]?.emailAddress ?? ""}
|
|
27294
|
+
</ExpoUIText>
|
|
27295
|
+
</Column>
|
|
27296
|
+
</Host>
|
|
26467
27297
|
<SignOutButton />
|
|
26468
27298
|
</View>
|
|
26469
27299
|
) : (
|
|
26470
27300
|
<View style={[styles.card, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
26471
|
-
<
|
|
26472
|
-
<
|
|
26473
|
-
|
|
26474
|
-
|
|
26475
|
-
|
|
26476
|
-
|
|
27301
|
+
<Host style={styles.authActionsHost} matchContents=\\{{ vertical: true }}>
|
|
27302
|
+
<Column spacing={8}>
|
|
27303
|
+
<Button
|
|
27304
|
+
label="Sign in"
|
|
27305
|
+
variant="outlined"
|
|
27306
|
+
onPress={() => router.push("/(auth)/sign-in")}
|
|
27307
|
+
/>
|
|
27308
|
+
<Button
|
|
27309
|
+
label="Sign up"
|
|
27310
|
+
onPress={() => router.push("/(auth)/sign-up")}
|
|
27311
|
+
/>
|
|
27312
|
+
</Column>
|
|
27313
|
+
</Host>
|
|
26477
27314
|
</View>
|
|
26478
27315
|
)}
|
|
26479
27316
|
{{/if}}
|
|
26480
27317
|
|
|
26481
27318
|
{{#if (and (eq backend "convex") (eq auth "better-auth"))}}
|
|
26482
|
-
{user ? (
|
|
26483
|
-
<View style={[styles.userCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
26484
|
-
<View style={styles.userHeader}>
|
|
26485
|
-
<Text style={[styles.userText, { color: theme.text }]}>
|
|
26486
|
-
Welcome, <Text style={styles.userName}>{user.name}</Text>
|
|
26487
|
-
</Text>
|
|
26488
|
-
</View>
|
|
26489
|
-
<Text style={[styles.userEmail, { color: theme.text, opacity: 0.7 }]}>
|
|
26490
|
-
{user.email}
|
|
26491
|
-
</Text>
|
|
26492
|
-
<TouchableOpacity style={[styles.signOutButton, { backgroundColor: theme.notification }]} onPress={()=> {
|
|
26493
|
-
authClient.signOut();
|
|
26494
|
-
}}
|
|
26495
|
-
>
|
|
26496
|
-
<Text style={styles.signOutText}>Sign Out</Text>
|
|
26497
|
-
</TouchableOpacity>
|
|
26498
|
-
</View>
|
|
26499
|
-
) : null}
|
|
26500
27319
|
<View style={[styles.statusCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
26501
|
-
<
|
|
26502
|
-
|
|
26503
|
-
|
|
27320
|
+
<Host style={styles.statusCardTitleHost} matchContents=\\{{ vertical: true }}>
|
|
27321
|
+
<ExpoUIText
|
|
27322
|
+
textStyle=\\{{ color: theme.text, fontSize: 16, fontWeight: "bold" }}
|
|
27323
|
+
>
|
|
27324
|
+
API Status
|
|
27325
|
+
</ExpoUIText>
|
|
27326
|
+
</Host>
|
|
26504
27327
|
<View style={styles.statusRow}>
|
|
26505
|
-
<View style={[styles.statusIndicator, { backgroundColor: healthCheck ? "#10b981" : "#
|
|
26506
|
-
<
|
|
26507
|
-
{
|
|
26508
|
-
|
|
26509
|
-
|
|
26510
|
-
|
|
26511
|
-
|
|
26512
|
-
|
|
27328
|
+
<View style={[styles.statusIndicator, { backgroundColor: healthCheck ? "#10b981" : "#f59e0b" }]} />
|
|
27329
|
+
<View style={styles.statusContent}>
|
|
27330
|
+
<Host matchContents=\\{{ vertical: true }}>
|
|
27331
|
+
<ExpoUIText
|
|
27332
|
+
textStyle=\\{{ color: theme.text, fontSize: 12 }}
|
|
27333
|
+
style=\\{{ opacity: 0.7 }}
|
|
27334
|
+
>
|
|
27335
|
+
{healthCheck === undefined
|
|
27336
|
+
? "Checking..."
|
|
27337
|
+
: healthCheck === "OK"
|
|
27338
|
+
? "Connected to API"
|
|
27339
|
+
: "API Disconnected"}
|
|
27340
|
+
</ExpoUIText>
|
|
27341
|
+
</Host>
|
|
27342
|
+
</View>
|
|
26513
27343
|
</View>
|
|
26514
27344
|
</View>
|
|
26515
|
-
|
|
27345
|
+
|
|
27346
|
+
{user ? (
|
|
27347
|
+
<View style={[styles.userCard, { backgroundColor: theme.card, borderColor: theme.border }]}>
|
|
27348
|
+
<Host style={styles.userHeader} matchContents>
|
|
27349
|
+
<Column spacing={6}>
|
|
27350
|
+
<ExpoUIText textStyle=\\{{ color: theme.text, fontSize: 16, fontWeight: "bold" }}>
|
|
27351
|
+
{\`Welcome, \${user.name}\`}
|
|
27352
|
+
</ExpoUIText>
|
|
27353
|
+
<ExpoUIText
|
|
27354
|
+
textStyle=\\{{ color: theme.text, fontSize: 14 }}
|
|
27355
|
+
style=\\{{ opacity: 0.7 }}
|
|
27356
|
+
>
|
|
27357
|
+
{user.email}
|
|
27358
|
+
</ExpoUIText>
|
|
27359
|
+
</Column>
|
|
27360
|
+
</Host>
|
|
27361
|
+
<Host matchContents=\\{{ vertical: true }}>
|
|
27362
|
+
<Button
|
|
27363
|
+
label="Sign Out"
|
|
27364
|
+
variant="outlined"
|
|
27365
|
+
onPress={() => {
|
|
27366
|
+
authClient.signOut();
|
|
27367
|
+
}}
|
|
27368
|
+
/>
|
|
27369
|
+
</Host>
|
|
27370
|
+
{{#if (eq payments "polar")}}
|
|
27371
|
+
<Host style={styles.paymentActions} matchContents=\\{{ vertical: true }}>
|
|
27372
|
+
<Column spacing={8}>
|
|
27373
|
+
{subscription ? (
|
|
27374
|
+
<Button
|
|
27375
|
+
label="Manage Subscription"
|
|
27376
|
+
variant="outlined"
|
|
27377
|
+
onPress={handlePolarPortal}
|
|
27378
|
+
/>
|
|
27379
|
+
) : (
|
|
27380
|
+
<Button label="Upgrade to Pro" onPress={handlePolarCheckout} />
|
|
27381
|
+
)}
|
|
27382
|
+
</Column>
|
|
27383
|
+
</Host>
|
|
27384
|
+
{{/if}}
|
|
27385
|
+
</View>
|
|
27386
|
+
) : (
|
|
26516
27387
|
<>
|
|
26517
27388
|
<SignIn />
|
|
26518
27389
|
<SignUp />
|
|
@@ -26530,12 +27401,14 @@ scrollView: {
|
|
|
26530
27401
|
flex: 1,
|
|
26531
27402
|
},
|
|
26532
27403
|
content: {
|
|
26533
|
-
|
|
27404
|
+
paddingHorizontal: 20,
|
|
27405
|
+
paddingTop: 28,
|
|
27406
|
+
paddingBottom: 32,
|
|
26534
27407
|
},
|
|
26535
|
-
|
|
26536
|
-
|
|
26537
|
-
|
|
26538
|
-
marginBottom:
|
|
27408
|
+
titleHost: {
|
|
27409
|
+
alignSelf: "stretch",
|
|
27410
|
+
height: 34,
|
|
27411
|
+
marginBottom: 24,
|
|
26539
27412
|
},
|
|
26540
27413
|
card: {
|
|
26541
27414
|
padding: 16,
|
|
@@ -26548,56 +27421,45 @@ alignItems: "center",
|
|
|
26548
27421
|
gap: 8,
|
|
26549
27422
|
},
|
|
26550
27423
|
statusIndicator: {
|
|
26551
|
-
height:
|
|
26552
|
-
width:
|
|
27424
|
+
height: 10,
|
|
27425
|
+
width: 10,
|
|
27426
|
+
borderRadius: 999,
|
|
26553
27427
|
},
|
|
26554
27428
|
statusContent: {
|
|
26555
27429
|
flex: 1,
|
|
26556
27430
|
},
|
|
26557
|
-
statusTitle: {
|
|
26558
|
-
fontSize: 14,
|
|
26559
|
-
fontWeight: "bold",
|
|
26560
|
-
},
|
|
26561
|
-
statusText: {
|
|
26562
|
-
fontSize: 12,
|
|
26563
|
-
},
|
|
26564
27431
|
userCard: {
|
|
26565
27432
|
marginBottom: 16,
|
|
26566
27433
|
padding: 16,
|
|
26567
27434
|
borderWidth: 1,
|
|
27435
|
+
borderRadius: 16,
|
|
26568
27436
|
},
|
|
26569
27437
|
userHeader: {
|
|
26570
27438
|
marginBottom: 8,
|
|
26571
27439
|
},
|
|
26572
|
-
|
|
26573
|
-
|
|
27440
|
+
paymentActions: {
|
|
27441
|
+
marginTop: 12,
|
|
26574
27442
|
},
|
|
26575
|
-
|
|
26576
|
-
fontWeight: "bold",
|
|
26577
|
-
},
|
|
26578
|
-
userEmail: {
|
|
26579
|
-
fontSize: 14,
|
|
27443
|
+
authHost: {
|
|
26580
27444
|
marginBottom: 12,
|
|
26581
27445
|
},
|
|
26582
|
-
|
|
26583
|
-
|
|
26584
|
-
},
|
|
26585
|
-
signOutText: {
|
|
26586
|
-
color: "#ffffff",
|
|
27446
|
+
authActionsHost: {
|
|
27447
|
+
marginTop: 4,
|
|
26587
27448
|
},
|
|
26588
27449
|
statusCard: {
|
|
26589
27450
|
marginBottom: 16,
|
|
26590
27451
|
padding: 16,
|
|
26591
27452
|
borderWidth: 1,
|
|
27453
|
+
borderRadius: 16,
|
|
26592
27454
|
},
|
|
26593
|
-
|
|
27455
|
+
statusCardTitleHost: {
|
|
26594
27456
|
marginBottom: 8,
|
|
26595
|
-
fontWeight: "bold",
|
|
26596
27457
|
},
|
|
26597
27458
|
});
|
|
26598
27459
|
`],
|
|
26599
27460
|
["frontend/native/bare/app/+not-found.tsx.hbs", `import { Container } from "@/components/container";
|
|
26600
|
-
import {
|
|
27461
|
+
import { Button, Column, Host, Text as ExpoUIText } from "@expo/ui";
|
|
27462
|
+
import { Stack, router } from "expo-router";
|
|
26601
27463
|
import { Text, View, StyleSheet } from "react-native";
|
|
26602
27464
|
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
26603
27465
|
import { NAV_THEME } from "@/lib/constants";
|
|
@@ -26613,17 +27475,26 @@ export default function NotFoundScreen() {
|
|
|
26613
27475
|
<View style={styles.container}>
|
|
26614
27476
|
<View style={styles.content}>
|
|
26615
27477
|
<Text style={styles.emoji}>🤔</Text>
|
|
26616
|
-
<
|
|
26617
|
-
|
|
26618
|
-
|
|
26619
|
-
|
|
26620
|
-
|
|
26621
|
-
|
|
26622
|
-
|
|
26623
|
-
|
|
26624
|
-
|
|
26625
|
-
|
|
26626
|
-
|
|
27478
|
+
<Host matchContents=\\{{ vertical: true }}>
|
|
27479
|
+
<Column spacing={12} alignment="center">
|
|
27480
|
+
<ExpoUIText
|
|
27481
|
+
textStyle=\\{{ color: theme.text, fontSize: 20, fontWeight: "bold", textAlign: "center" }}
|
|
27482
|
+
>
|
|
27483
|
+
Page Not Found
|
|
27484
|
+
</ExpoUIText>
|
|
27485
|
+
<ExpoUIText
|
|
27486
|
+
textStyle=\\{{ color: theme.text, fontSize: 14, textAlign: "center" }}
|
|
27487
|
+
style=\\{{ opacity: 0.7 }}
|
|
27488
|
+
>
|
|
27489
|
+
Sorry, the page you're looking for doesn't exist.
|
|
27490
|
+
</ExpoUIText>
|
|
27491
|
+
<Button
|
|
27492
|
+
label="Go to Home"
|
|
27493
|
+
variant="outlined"
|
|
27494
|
+
onPress={() => router.replace("/")}
|
|
27495
|
+
/>
|
|
27496
|
+
</Column>
|
|
27497
|
+
</Host>
|
|
26627
27498
|
</View>
|
|
26628
27499
|
</View>
|
|
26629
27500
|
</Container>
|
|
@@ -26645,25 +27516,11 @@ const styles = StyleSheet.create({
|
|
|
26645
27516
|
fontSize: 48,
|
|
26646
27517
|
marginBottom: 16,
|
|
26647
27518
|
},
|
|
26648
|
-
title: {
|
|
26649
|
-
fontSize: 20,
|
|
26650
|
-
fontWeight: "bold",
|
|
26651
|
-
marginBottom: 8,
|
|
26652
|
-
textAlign: "center",
|
|
26653
|
-
},
|
|
26654
|
-
subtitle: {
|
|
26655
|
-
fontSize: 14,
|
|
26656
|
-
textAlign: "center",
|
|
26657
|
-
marginBottom: 24,
|
|
26658
|
-
},
|
|
26659
|
-
link: {
|
|
26660
|
-
padding: 12,
|
|
26661
|
-
},
|
|
26662
27519
|
});
|
|
26663
|
-
|
|
26664
27520
|
`],
|
|
26665
27521
|
["frontend/native/bare/app/modal.tsx.hbs", `import { Container } from "@/components/container";
|
|
26666
|
-
import {
|
|
27522
|
+
import { Button, Column, Host, Text as ExpoUIText } from "@expo/ui";
|
|
27523
|
+
import { View, StyleSheet } from "react-native";
|
|
26667
27524
|
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
26668
27525
|
import { NAV_THEME } from "@/lib/constants";
|
|
26669
27526
|
|
|
@@ -26674,9 +27531,22 @@ export default function Modal() {
|
|
|
26674
27531
|
return (
|
|
26675
27532
|
<Container>
|
|
26676
27533
|
<View style={styles.container}>
|
|
26677
|
-
<
|
|
26678
|
-
<
|
|
26679
|
-
|
|
27534
|
+
<Host style={styles.expoUiHost}>
|
|
27535
|
+
<Column spacing={12} alignment="center">
|
|
27536
|
+
<ExpoUIText
|
|
27537
|
+
textStyle=\\{{ color: theme.text, fontSize: 20, fontWeight: "bold" }}
|
|
27538
|
+
>
|
|
27539
|
+
Modal
|
|
27540
|
+
</ExpoUIText>
|
|
27541
|
+
<ExpoUIText
|
|
27542
|
+
textStyle=\\{{ color: theme.text, fontSize: 14, textAlign: "center" }}
|
|
27543
|
+
style=\\{{ opacity: 0.7 }}
|
|
27544
|
+
>
|
|
27545
|
+
Built with Expo UI universal components
|
|
27546
|
+
</ExpoUIText>
|
|
27547
|
+
<Button label="Native control" onPress={() => null} />
|
|
27548
|
+
</Column>
|
|
27549
|
+
</Host>
|
|
26680
27550
|
</View>
|
|
26681
27551
|
</Container>
|
|
26682
27552
|
);
|
|
@@ -26687,15 +27557,11 @@ const styles = StyleSheet.create({
|
|
|
26687
27557
|
flex: 1,
|
|
26688
27558
|
padding: 16,
|
|
26689
27559
|
},
|
|
26690
|
-
|
|
26691
|
-
|
|
26692
|
-
|
|
26693
|
-
title: {
|
|
26694
|
-
fontSize: 20,
|
|
26695
|
-
fontWeight: "bold",
|
|
27560
|
+
expoUiHost: {
|
|
27561
|
+
alignSelf: "stretch",
|
|
27562
|
+
padding: 16,
|
|
26696
27563
|
},
|
|
26697
27564
|
});
|
|
26698
|
-
|
|
26699
27565
|
`],
|
|
26700
27566
|
["frontend/native/bare/components/container.tsx.hbs", `import React from "react";
|
|
26701
27567
|
import { SafeAreaView } from "react-native-safe-area-context";
|
|
@@ -26710,7 +27576,10 @@ export function Container({ children }: { children: React.ReactNode }) {
|
|
|
26710
27576
|
: NAV_THEME.light.background;
|
|
26711
27577
|
|
|
26712
27578
|
return (
|
|
26713
|
-
<SafeAreaView
|
|
27579
|
+
<SafeAreaView
|
|
27580
|
+
edges={["left", "right", "bottom"]}
|
|
27581
|
+
style={[styles.container, { backgroundColor }]}
|
|
27582
|
+
>
|
|
26714
27583
|
{children}
|
|
26715
27584
|
</SafeAreaView>
|
|
26716
27585
|
);
|
|
@@ -26721,7 +27590,6 @@ const styles = StyleSheet.create({
|
|
|
26721
27590
|
flex: 1,
|
|
26722
27591
|
},
|
|
26723
27592
|
});
|
|
26724
|
-
|
|
26725
27593
|
`],
|
|
26726
27594
|
["frontend/native/bare/components/header-button.tsx.hbs", `import FontAwesome from "@expo/vector-icons/FontAwesome";
|
|
26727
27595
|
import { forwardRef } from "react";
|
|
@@ -26773,13 +27641,14 @@ const styles = StyleSheet.create({
|
|
|
26773
27641
|
`],
|
|
26774
27642
|
["frontend/native/bare/components/tabbar-icon.tsx.hbs", `import FontAwesome from "@expo/vector-icons/FontAwesome";
|
|
26775
27643
|
|
|
27644
|
+
type FontAwesomeProps = React.ComponentProps<typeof FontAwesome>;
|
|
27645
|
+
|
|
26776
27646
|
export const TabBarIcon = (props: {
|
|
26777
|
-
name:
|
|
26778
|
-
color:
|
|
27647
|
+
name: FontAwesomeProps["name"];
|
|
27648
|
+
color: FontAwesomeProps["color"];
|
|
26779
27649
|
}) => {
|
|
26780
27650
|
return <FontAwesome size={24} style=\\{{ marginBottom: -3 }} {...props} />;
|
|
26781
27651
|
};
|
|
26782
|
-
|
|
26783
27652
|
`],
|
|
26784
27653
|
["frontend/native/bare/lib/constants.ts.hbs", `export const NAV_THEME = {
|
|
26785
27654
|
light: {
|
|
@@ -26841,41 +27710,39 @@ module.exports = config;
|
|
|
26841
27710
|
"web": "expo start --web"
|
|
26842
27711
|
},
|
|
26843
27712
|
"dependencies": {
|
|
27713
|
+
"@expo/ui": "~56.0.12",
|
|
26844
27714
|
"@expo/vector-icons": "^15.1.1",
|
|
26845
|
-
"@react-navigation/bottom-tabs": "^7.15.9",
|
|
26846
|
-
"@react-navigation/drawer": "^7.9.4",
|
|
26847
|
-
"@react-navigation/native": "^7.2.2",
|
|
26848
27715
|
"@tanstack/react-query": "^5.99.2",
|
|
26849
27716
|
{{#if (includes examples "ai")}}
|
|
26850
27717
|
"@stardazed/streams-text-encoding": "^1.0.2",
|
|
26851
27718
|
"@ungap/structured-clone": "^1.3.0",
|
|
26852
27719
|
{{/if}}
|
|
26853
|
-
"expo": "
|
|
26854
|
-
"expo-constants": "~
|
|
26855
|
-
"expo-crypto": "~
|
|
26856
|
-
"expo-font": "~
|
|
26857
|
-
"expo-linking": "~
|
|
26858
|
-
"expo-network": "~
|
|
26859
|
-
"expo-router": "~
|
|
26860
|
-
"expo-secure-store": "~
|
|
26861
|
-
"expo-splash-screen": "~
|
|
26862
|
-
"expo-status-bar": "~
|
|
26863
|
-
"expo-system-ui": "~
|
|
26864
|
-
"expo-web-browser": "~
|
|
26865
|
-
"react": "19.2.
|
|
26866
|
-
"react-dom": "19.2.
|
|
26867
|
-
"react-native": "0.
|
|
26868
|
-
"react-native-gesture-handler": "~2.
|
|
26869
|
-
"react-native-reanimated": "4.
|
|
26870
|
-
"react-native-safe-area-context": "~5.
|
|
26871
|
-
"react-native-screens": "
|
|
27720
|
+
"expo": "~56.0.3",
|
|
27721
|
+
"expo-constants": "~56.0.14",
|
|
27722
|
+
"expo-crypto": "~56.0.3",
|
|
27723
|
+
"expo-font": "~56.0.5",
|
|
27724
|
+
"expo-linking": "~56.0.11",
|
|
27725
|
+
"expo-network": "~56.0.4",
|
|
27726
|
+
"expo-router": "~56.2.5",
|
|
27727
|
+
"expo-secure-store": "~56.0.4",
|
|
27728
|
+
"expo-splash-screen": "~56.0.9",
|
|
27729
|
+
"expo-status-bar": "~56.0.4",
|
|
27730
|
+
"expo-system-ui": "~56.0.5",
|
|
27731
|
+
"expo-web-browser": "~56.0.5",
|
|
27732
|
+
"react": "19.2.3",
|
|
27733
|
+
"react-dom": "19.2.3",
|
|
27734
|
+
"react-native": "0.85.3",
|
|
27735
|
+
"react-native-gesture-handler": "~2.31.1",
|
|
27736
|
+
"react-native-reanimated": "4.3.1",
|
|
27737
|
+
"react-native-safe-area-context": "~5.7.0",
|
|
27738
|
+
"react-native-screens": "4.25.2",
|
|
26872
27739
|
"react-native-web": "~0.21.0",
|
|
26873
|
-
"react-native-worklets": "0.
|
|
27740
|
+
"react-native-worklets": "0.8.3"
|
|
26874
27741
|
},
|
|
26875
27742
|
"devDependencies": {
|
|
26876
|
-
"@babel/core": "^7.
|
|
26877
|
-
"@types/react": "~19.2.
|
|
26878
|
-
"typescript": "
|
|
27743
|
+
"@babel/core": "^7.29.0",
|
|
27744
|
+
"@types/react": "~19.2.14",
|
|
27745
|
+
"typescript": "^6"
|
|
26879
27746
|
},
|
|
26880
27747
|
"private": true
|
|
26881
27748
|
}
|
|
@@ -27023,9 +27890,6 @@ export const unstable_settings = {
|
|
|
27023
27890
|
|
|
27024
27891
|
{{#if (eq backend "convex")}}
|
|
27025
27892
|
const convex = new ConvexReactClient(env.EXPO_PUBLIC_CONVEX_URL, {
|
|
27026
|
-
{{#if (eq auth "better-auth")}}
|
|
27027
|
-
expectAuth: true,
|
|
27028
|
-
{{/if}}
|
|
27029
27893
|
unsavedChangesWarning: false,
|
|
27030
27894
|
});
|
|
27031
27895
|
{{/if}}
|
|
@@ -27430,7 +28294,12 @@ const styles = StyleSheet.create((theme) => ({
|
|
|
27430
28294
|
},
|
|
27431
28295
|
}));
|
|
27432
28296
|
`],
|
|
27433
|
-
["frontend/native/unistyles/app/(drawer)/index.tsx.hbs", `import { ScrollView, Text, View, TouchableOpacity } from "react-native";
|
|
28297
|
+
["frontend/native/unistyles/app/(drawer)/index.tsx.hbs", `import { ScrollView, Text, View, TouchableOpacity{{#if (and (eq backend "convex") (eq auth "better-auth") (eq payments "polar"))}}, Alert{{/if}} } from "react-native";
|
|
28298
|
+
{{#if (and (eq backend "convex") (eq auth "better-auth") (eq payments "polar"))}}
|
|
28299
|
+
import * as Linking from "expo-linking";
|
|
28300
|
+
import * as WebBrowser from "expo-web-browser";
|
|
28301
|
+
import { env } from "@{{projectName}}/env/native";
|
|
28302
|
+
{{/if}}
|
|
27434
28303
|
import { StyleSheet } from "react-native-unistyles";
|
|
27435
28304
|
import { Container } from "@/components/container";
|
|
27436
28305
|
|
|
@@ -27453,7 +28322,7 @@ import { Link } from "expo-router";
|
|
|
27453
28322
|
import { useAuth, useUser } from "@clerk/expo";
|
|
27454
28323
|
import { SignOutButton } from "@/components/sign-out-button";
|
|
27455
28324
|
{{else if (and (eq backend "convex") (eq auth "better-auth"))}}
|
|
27456
|
-
import { useConvexAuth, useQuery } from "convex/react";
|
|
28325
|
+
import { {{#if (eq payments "polar")}}useAction, {{/if}}useConvexAuth, useQuery } from "convex/react";
|
|
27457
28326
|
import { api } from "@{{ projectName }}/backend/convex/_generated/api";
|
|
27458
28327
|
import { authClient } from "@/lib/auth-client";
|
|
27459
28328
|
import { SignIn } from "@/components/sign-in";
|
|
@@ -27481,6 +28350,57 @@ export default function Home() {
|
|
|
27481
28350
|
const healthCheck = useQuery(api.healthCheck.get);
|
|
27482
28351
|
const { isAuthenticated } = useConvexAuth();
|
|
27483
28352
|
const user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : "skip");
|
|
28353
|
+
{{#if (eq payments "polar")}}
|
|
28354
|
+
const products = useQuery(api.polar.listAllProducts);
|
|
28355
|
+
const subscription = useQuery(api.polar.getCurrentSubscription);
|
|
28356
|
+
const generateCheckoutLink = useAction(api.polar.generateCheckoutLink);
|
|
28357
|
+
const generateCustomerPortalUrl = useAction(api.polar.generateCustomerPortalUrl);
|
|
28358
|
+
const recurringProduct = products?.find((product) => product.isRecurring);
|
|
28359
|
+
|
|
28360
|
+
const openPolarLink = async (url: string, returnUrl: string) => {
|
|
28361
|
+
await WebBrowser.openAuthSessionAsync(url, returnUrl);
|
|
28362
|
+
};
|
|
28363
|
+
|
|
28364
|
+
const getPolarReturnUrl = (returnUrl: string) => {
|
|
28365
|
+
const url = new URL("/polar/success", env.EXPO_PUBLIC_CONVEX_SITE_URL);
|
|
28366
|
+
url.searchParams.set("returnUrl", returnUrl);
|
|
28367
|
+
return url.toString();
|
|
28368
|
+
};
|
|
28369
|
+
|
|
28370
|
+
const handlePolarCheckout = async () => {
|
|
28371
|
+
try {
|
|
28372
|
+
if (!recurringProduct) {
|
|
28373
|
+
Alert.alert("Checkout unavailable", "No recurring Polar product is available yet.");
|
|
28374
|
+
return;
|
|
28375
|
+
}
|
|
28376
|
+
|
|
28377
|
+
const returnUrl = Linking.createURL("/");
|
|
28378
|
+
const polarReturnUrl = getPolarReturnUrl(returnUrl);
|
|
28379
|
+
const { url } = await generateCheckoutLink({
|
|
28380
|
+
productIds: [recurringProduct.id],
|
|
28381
|
+
origin: env.EXPO_PUBLIC_CONVEX_SITE_URL,
|
|
28382
|
+
successUrl: polarReturnUrl,
|
|
28383
|
+
});
|
|
28384
|
+
|
|
28385
|
+
await openPolarLink(url, returnUrl);
|
|
28386
|
+
} catch {
|
|
28387
|
+
Alert.alert("Checkout failed", "Unable to open Polar checkout. Please try again.");
|
|
28388
|
+
}
|
|
28389
|
+
};
|
|
28390
|
+
|
|
28391
|
+
const handlePolarPortal = async () => {
|
|
28392
|
+
try {
|
|
28393
|
+
const returnUrl = Linking.createURL("/");
|
|
28394
|
+
const { url } = await generateCustomerPortalUrl({
|
|
28395
|
+
returnUrl: getPolarReturnUrl(returnUrl),
|
|
28396
|
+
});
|
|
28397
|
+
|
|
28398
|
+
await openPolarLink(url, returnUrl);
|
|
28399
|
+
} catch {
|
|
28400
|
+
Alert.alert("Portal unavailable", "Unable to open the customer portal. Please try again.");
|
|
28401
|
+
}
|
|
28402
|
+
};
|
|
28403
|
+
{{/if}}
|
|
27484
28404
|
{{else if (eq backend "convex")}}
|
|
27485
28405
|
const healthCheck = useQuery(api.healthCheck.get);
|
|
27486
28406
|
{{/if}}
|
|
@@ -27623,6 +28543,25 @@ export default function Home() {
|
|
|
27623
28543
|
>
|
|
27624
28544
|
<Text style={styles.signOutText}>Sign Out</Text>
|
|
27625
28545
|
</TouchableOpacity>
|
|
28546
|
+
{{#if (eq payments "polar")}}
|
|
28547
|
+
<View style={styles.paymentActions}>
|
|
28548
|
+
{subscription ? (
|
|
28549
|
+
<TouchableOpacity
|
|
28550
|
+
style={styles.polarSecondaryButton}
|
|
28551
|
+
onPress={handlePolarPortal}
|
|
28552
|
+
>
|
|
28553
|
+
<Text style={styles.polarSecondaryButtonText}>Manage Subscription</Text>
|
|
28554
|
+
</TouchableOpacity>
|
|
28555
|
+
) : (
|
|
28556
|
+
<TouchableOpacity
|
|
28557
|
+
style={styles.polarPrimaryButton}
|
|
28558
|
+
onPress={handlePolarCheckout}
|
|
28559
|
+
>
|
|
28560
|
+
<Text style={styles.polarPrimaryButtonText}>Upgrade to Pro</Text>
|
|
28561
|
+
</TouchableOpacity>
|
|
28562
|
+
)}
|
|
28563
|
+
</View>
|
|
28564
|
+
{{/if}}
|
|
27626
28565
|
</View>
|
|
27627
28566
|
) : null}
|
|
27628
28567
|
<View style={styles.apiStatusCard}>
|
|
@@ -27775,6 +28714,31 @@ const styles = StyleSheet.create((theme) => ({
|
|
|
27775
28714
|
color: theme.colors.destructiveForeground,
|
|
27776
28715
|
fontWeight: "500",
|
|
27777
28716
|
},
|
|
28717
|
+
paymentActions: {
|
|
28718
|
+
marginTop: theme.spacing.sm,
|
|
28719
|
+
alignItems: "flex-start",
|
|
28720
|
+
},
|
|
28721
|
+
polarPrimaryButton: {
|
|
28722
|
+
backgroundColor: theme.colors.primary,
|
|
28723
|
+
paddingVertical: theme.spacing.sm,
|
|
28724
|
+
paddingHorizontal: theme.spacing.md,
|
|
28725
|
+
borderRadius: theme.borderRadius.md,
|
|
28726
|
+
},
|
|
28727
|
+
polarPrimaryButtonText: {
|
|
28728
|
+
color: theme.colors.primaryForeground,
|
|
28729
|
+
fontWeight: "500",
|
|
28730
|
+
},
|
|
28731
|
+
polarSecondaryButton: {
|
|
28732
|
+
borderWidth: 1,
|
|
28733
|
+
borderColor: theme.colors.border,
|
|
28734
|
+
paddingVertical: theme.spacing.sm,
|
|
28735
|
+
paddingHorizontal: theme.spacing.md,
|
|
28736
|
+
borderRadius: theme.borderRadius.md,
|
|
28737
|
+
},
|
|
28738
|
+
polarSecondaryButtonText: {
|
|
28739
|
+
color: theme.colors.foreground,
|
|
28740
|
+
fontWeight: "500",
|
|
28741
|
+
},
|
|
27778
28742
|
apiStatusCard: {
|
|
27779
28743
|
marginBottom: theme.spacing.md,
|
|
27780
28744
|
borderRadius: theme.borderRadius.lg,
|
|
@@ -28017,9 +28981,11 @@ const styles = StyleSheet.create((theme) => ({
|
|
|
28017
28981
|
`],
|
|
28018
28982
|
["frontend/native/unistyles/components/tabbar-icon.tsx.hbs", `import FontAwesome from "@expo/vector-icons/FontAwesome";
|
|
28019
28983
|
|
|
28984
|
+
type FontAwesomeProps = React.ComponentProps<typeof FontAwesome>;
|
|
28985
|
+
|
|
28020
28986
|
export const TabBarIcon = (props: {
|
|
28021
|
-
name:
|
|
28022
|
-
color:
|
|
28987
|
+
name: FontAwesomeProps["name"];
|
|
28988
|
+
color: FontAwesomeProps["color"];
|
|
28023
28989
|
}) => {
|
|
28024
28990
|
return <FontAwesome size={24} style=\\{{ marginBottom: -3 }} {...props} />;
|
|
28025
28991
|
};
|
|
@@ -28046,45 +29012,42 @@ module.exports = config;
|
|
|
28046
29012
|
},
|
|
28047
29013
|
"dependencies": {
|
|
28048
29014
|
"@expo/vector-icons": "^15.1.1",
|
|
28049
|
-
"@react-navigation/bottom-tabs": "^7.15.9",
|
|
28050
|
-
"@react-navigation/drawer": "^7.9.4",
|
|
28051
|
-
"@react-navigation/native": "^7.2.2",
|
|
28052
29015
|
{{#if (includes examples "ai")}}
|
|
28053
29016
|
"@stardazed/streams-text-encoding": "^1.0.2",
|
|
28054
29017
|
"@ungap/structured-clone": "^1.3.0",
|
|
28055
29018
|
{{/if}}
|
|
28056
|
-
"babel-preset-expo": "~
|
|
28057
|
-
"expo": "
|
|
28058
|
-
"expo-constants": "~
|
|
28059
|
-
"expo-crypto": "~
|
|
28060
|
-
"expo-dev-client": "~
|
|
28061
|
-
"expo-font": "~
|
|
28062
|
-
"expo-linking": "~
|
|
28063
|
-
"expo-network": "~
|
|
28064
|
-
"expo-router": "~
|
|
28065
|
-
"expo-secure-store": "~
|
|
28066
|
-
"expo-splash-screen": "~
|
|
28067
|
-
"expo-status-bar": "~
|
|
28068
|
-
"expo-system-ui": "~
|
|
28069
|
-
"expo-web-browser": "~
|
|
28070
|
-
"react": "19.2.
|
|
28071
|
-
"react-dom": "19.2.
|
|
28072
|
-
"react-native": "0.
|
|
29019
|
+
"babel-preset-expo": "~56.0.0",
|
|
29020
|
+
"expo": "~56.0.3",
|
|
29021
|
+
"expo-constants": "~56.0.14",
|
|
29022
|
+
"expo-crypto": "~56.0.3",
|
|
29023
|
+
"expo-dev-client": "~56.0.14",
|
|
29024
|
+
"expo-font": "~56.0.5",
|
|
29025
|
+
"expo-linking": "~56.0.11",
|
|
29026
|
+
"expo-network": "~56.0.4",
|
|
29027
|
+
"expo-router": "~56.2.5",
|
|
29028
|
+
"expo-secure-store": "~56.0.4",
|
|
29029
|
+
"expo-splash-screen": "~56.0.9",
|
|
29030
|
+
"expo-status-bar": "~56.0.4",
|
|
29031
|
+
"expo-system-ui": "~56.0.5",
|
|
29032
|
+
"expo-web-browser": "~56.0.5",
|
|
29033
|
+
"react": "19.2.3",
|
|
29034
|
+
"react-dom": "19.2.3",
|
|
29035
|
+
"react-native": "0.85.3",
|
|
28073
29036
|
"react-native-edge-to-edge": "^1.8.1",
|
|
28074
|
-
"react-native-gesture-handler": "~2.
|
|
28075
|
-
"react-native-nitro-modules": "^0.35.
|
|
28076
|
-
"react-native-reanimated": "4.
|
|
28077
|
-
"react-native-safe-area-context": "~5.
|
|
28078
|
-
"react-native-screens": "
|
|
28079
|
-
"react-native-unistyles": "^3.2.
|
|
29037
|
+
"react-native-gesture-handler": "~2.31.1",
|
|
29038
|
+
"react-native-nitro-modules": "^0.35.7",
|
|
29039
|
+
"react-native-reanimated": "4.3.1",
|
|
29040
|
+
"react-native-safe-area-context": "~5.7.0",
|
|
29041
|
+
"react-native-screens": "4.25.2",
|
|
29042
|
+
"react-native-unistyles": "^3.2.4",
|
|
28080
29043
|
"react-native-web": "~0.21.0",
|
|
28081
|
-
"react-native-worklets": "0.
|
|
29044
|
+
"react-native-worklets": "0.8.3"
|
|
28082
29045
|
},
|
|
28083
29046
|
"devDependencies": {
|
|
28084
|
-
"ajv": "^8.
|
|
28085
|
-
"@babel/core": "^7.
|
|
28086
|
-
"@types/react": "~19.2.
|
|
28087
|
-
"typescript": "
|
|
29047
|
+
"ajv": "^8.20.0",
|
|
29048
|
+
"@babel/core": "^7.29.0",
|
|
29049
|
+
"@types/react": "~19.2.14",
|
|
29050
|
+
"typescript": "^6"
|
|
28088
29051
|
}
|
|
28089
29052
|
}
|
|
28090
29053
|
`],
|
|
@@ -28194,7 +29157,7 @@ export const darkTheme = {
|
|
|
28194
29157
|
"strict": true,
|
|
28195
29158
|
"jsx": "react-jsx",
|
|
28196
29159
|
"paths": {
|
|
28197
|
-
"@/*": ["
|
|
29160
|
+
"@/*": ["./*"]
|
|
28198
29161
|
}
|
|
28199
29162
|
},
|
|
28200
29163
|
"include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"]
|
|
@@ -28326,9 +29289,6 @@ export const unstable_settings = {
|
|
|
28326
29289
|
|
|
28327
29290
|
{{#if (eq backend "convex")}}
|
|
28328
29291
|
const convex = new ConvexReactClient(env.EXPO_PUBLIC_CONVEX_URL, {
|
|
28329
|
-
{{#if (eq auth "better-auth")}}
|
|
28330
|
-
expectAuth: true,
|
|
28331
|
-
{{/if}}
|
|
28332
29292
|
unsavedChangesWarning: false,
|
|
28333
29293
|
});
|
|
28334
29294
|
{{/if}}
|
|
@@ -28580,7 +29540,7 @@ export default function TabLayout() {
|
|
|
28580
29540
|
name="index"
|
|
28581
29541
|
options=\\{{
|
|
28582
29542
|
title: "Home",
|
|
28583
|
-
tabBarIcon: ({ color, size }
|
|
29543
|
+
tabBarIcon: ({ color, size }) => (
|
|
28584
29544
|
<Ionicons name="home" size={size} color={color} />
|
|
28585
29545
|
),
|
|
28586
29546
|
}}
|
|
@@ -28589,7 +29549,7 @@ export default function TabLayout() {
|
|
|
28589
29549
|
name="two"
|
|
28590
29550
|
options=\\{{
|
|
28591
29551
|
title: "Explore",
|
|
28592
|
-
tabBarIcon: ({ color, size }
|
|
29552
|
+
tabBarIcon: ({ color, size }) => (
|
|
28593
29553
|
<Ionicons name="compass" size={size} color={color} />
|
|
28594
29554
|
),
|
|
28595
29555
|
}}
|
|
@@ -28630,7 +29590,12 @@ export default function TabTwo() {
|
|
|
28630
29590
|
);
|
|
28631
29591
|
}
|
|
28632
29592
|
`],
|
|
28633
|
-
["frontend/native/uniwind/app/(drawer)/index.tsx.hbs", `import { Text, View } from "react-native";
|
|
29593
|
+
["frontend/native/uniwind/app/(drawer)/index.tsx.hbs", `import { Text, View{{#if (and (eq backend "convex") (eq auth "better-auth") (eq payments "polar"))}}, Alert{{/if}} } from "react-native";
|
|
29594
|
+
{{#if (and (eq backend "convex") (eq auth "better-auth") (eq payments "polar"))}}
|
|
29595
|
+
import * as Linking from "expo-linking";
|
|
29596
|
+
import * as WebBrowser from "expo-web-browser";
|
|
29597
|
+
import { env } from "@{{projectName}}/env/native";
|
|
29598
|
+
{{/if}}
|
|
28634
29599
|
import { Container } from "@/components/container";
|
|
28635
29600
|
{{#if (eq api "orpc")}}
|
|
28636
29601
|
import { useQuery } from "@tanstack/react-query";
|
|
@@ -28651,7 +29616,7 @@ import { Link } from "expo-router";
|
|
|
28651
29616
|
import { useAuth, useUser } from "@clerk/expo";
|
|
28652
29617
|
import { SignOutButton } from "@/components/sign-out-button";
|
|
28653
29618
|
{{else if (and (eq backend "convex") (eq auth "better-auth"))}}
|
|
28654
|
-
import { useConvexAuth, useQuery } from "convex/react";
|
|
29619
|
+
import { {{#if (eq payments "polar")}}useAction, {{/if}}useConvexAuth, useQuery } from "convex/react";
|
|
28655
29620
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
28656
29621
|
import { authClient } from "@/lib/auth-client";
|
|
28657
29622
|
import { SignIn } from "@/components/sign-in";
|
|
@@ -28683,6 +29648,57 @@ const { user } = useUser();
|
|
|
28683
29648
|
const healthCheck = useQuery(api.healthCheck.get);
|
|
28684
29649
|
const { isAuthenticated } = useConvexAuth();
|
|
28685
29650
|
const user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : "skip");
|
|
29651
|
+
{{#if (eq payments "polar")}}
|
|
29652
|
+
const products = useQuery(api.polar.listAllProducts);
|
|
29653
|
+
const subscription = useQuery(api.polar.getCurrentSubscription);
|
|
29654
|
+
const generateCheckoutLink = useAction(api.polar.generateCheckoutLink);
|
|
29655
|
+
const generateCustomerPortalUrl = useAction(api.polar.generateCustomerPortalUrl);
|
|
29656
|
+
const recurringProduct = products?.find((product) => product.isRecurring);
|
|
29657
|
+
|
|
29658
|
+
const openPolarLink = async (url: string, returnUrl: string) => {
|
|
29659
|
+
await WebBrowser.openAuthSessionAsync(url, returnUrl);
|
|
29660
|
+
};
|
|
29661
|
+
|
|
29662
|
+
const getPolarReturnUrl = (returnUrl: string) => {
|
|
29663
|
+
const url = new URL("/polar/success", env.EXPO_PUBLIC_CONVEX_SITE_URL);
|
|
29664
|
+
url.searchParams.set("returnUrl", returnUrl);
|
|
29665
|
+
return url.toString();
|
|
29666
|
+
};
|
|
29667
|
+
|
|
29668
|
+
const handlePolarCheckout = async () => {
|
|
29669
|
+
try {
|
|
29670
|
+
if (!recurringProduct) {
|
|
29671
|
+
Alert.alert("Checkout unavailable", "No recurring Polar product is available yet.");
|
|
29672
|
+
return;
|
|
29673
|
+
}
|
|
29674
|
+
|
|
29675
|
+
const returnUrl = Linking.createURL("/");
|
|
29676
|
+
const polarReturnUrl = getPolarReturnUrl(returnUrl);
|
|
29677
|
+
const { url } = await generateCheckoutLink({
|
|
29678
|
+
productIds: [recurringProduct.id],
|
|
29679
|
+
origin: env.EXPO_PUBLIC_CONVEX_SITE_URL,
|
|
29680
|
+
successUrl: polarReturnUrl,
|
|
29681
|
+
});
|
|
29682
|
+
|
|
29683
|
+
await openPolarLink(url, returnUrl);
|
|
29684
|
+
} catch {
|
|
29685
|
+
Alert.alert("Checkout failed", "Unable to open Polar checkout. Please try again.");
|
|
29686
|
+
}
|
|
29687
|
+
};
|
|
29688
|
+
|
|
29689
|
+
const handlePolarPortal = async () => {
|
|
29690
|
+
try {
|
|
29691
|
+
const returnUrl = Linking.createURL("/");
|
|
29692
|
+
const { url } = await generateCustomerPortalUrl({
|
|
29693
|
+
returnUrl: getPolarReturnUrl(returnUrl),
|
|
29694
|
+
});
|
|
29695
|
+
|
|
29696
|
+
await openPolarLink(url, returnUrl);
|
|
29697
|
+
} catch {
|
|
29698
|
+
Alert.alert("Portal unavailable", "Unable to open the customer portal. Please try again.");
|
|
29699
|
+
}
|
|
29700
|
+
};
|
|
29701
|
+
{{/if}}
|
|
28686
29702
|
{{else if (eq backend "convex")}}
|
|
28687
29703
|
const healthCheck = useQuery(api.healthCheck.get);
|
|
28688
29704
|
{{/if}}
|
|
@@ -28834,6 +29850,19 @@ return (
|
|
|
28834
29850
|
Sign Out
|
|
28835
29851
|
</Button>
|
|
28836
29852
|
</View>
|
|
29853
|
+
{{#if (eq payments "polar")}}
|
|
29854
|
+
<View className="mt-4 gap-3">
|
|
29855
|
+
{subscription ? (
|
|
29856
|
+
<Button variant="secondary" onPress={handlePolarPortal}>
|
|
29857
|
+
Manage Subscription
|
|
29858
|
+
</Button>
|
|
29859
|
+
) : (
|
|
29860
|
+
<Button onPress={handlePolarCheckout}>
|
|
29861
|
+
Upgrade to Pro
|
|
29862
|
+
</Button>
|
|
29863
|
+
)}
|
|
29864
|
+
</View>
|
|
29865
|
+
{{/if}}
|
|
28837
29866
|
</Surface>
|
|
28838
29867
|
) : null}
|
|
28839
29868
|
<Surface variant="secondary" className="p-4 rounded-xl">
|
|
@@ -29106,46 +30135,44 @@ module.exports = uniwindConfig;
|
|
|
29106
30135
|
"web": "expo start --web"
|
|
29107
30136
|
},
|
|
29108
30137
|
"dependencies": {
|
|
29109
|
-
"@expo/metro-runtime": "~
|
|
30138
|
+
"@expo/metro-runtime": "~56.0.11",
|
|
29110
30139
|
"@expo/vector-icons": "^15.1.1",
|
|
29111
|
-
"@gorhom/bottom-sheet": "^5.2.
|
|
29112
|
-
"@react-navigation/drawer": "^7.9.4",
|
|
29113
|
-
"@react-navigation/elements": "^2.9.14",
|
|
30140
|
+
"@gorhom/bottom-sheet": "^5.2.14",
|
|
29114
30141
|
{{#if (includes examples "ai")}}
|
|
29115
30142
|
"@stardazed/streams-text-encoding": "^1.0.2",
|
|
29116
30143
|
"@ungap/structured-clone": "^1.3.0",
|
|
29117
30144
|
{{/if}}
|
|
29118
|
-
"expo": "
|
|
29119
|
-
"expo-constants": "~
|
|
29120
|
-
"expo-font": "~
|
|
29121
|
-
"expo-haptics": "~
|
|
29122
|
-
"expo-linking": "~
|
|
29123
|
-
"expo-network": "~
|
|
29124
|
-
"expo-router": "~
|
|
29125
|
-
"expo-secure-store": "~
|
|
29126
|
-
"expo-status-bar": "~
|
|
29127
|
-
"expo-web-browser": "~
|
|
29128
|
-
"heroui-native": "^1.0.
|
|
29129
|
-
"react": "19.2.
|
|
29130
|
-
"react-dom": "19.2.
|
|
29131
|
-
"react-native": "0.
|
|
29132
|
-
"react-native-gesture-handler": "~2.
|
|
29133
|
-
"react-native-keyboard-controller": "1.
|
|
29134
|
-
"react-native-reanimated": "4.
|
|
29135
|
-
"react-native-safe-area-context": "~5.
|
|
29136
|
-
"react-native-screens": "
|
|
29137
|
-
"react-native-svg": "15.15.
|
|
30145
|
+
"expo": "~56.0.3",
|
|
30146
|
+
"expo-constants": "~56.0.14",
|
|
30147
|
+
"expo-font": "~56.0.5",
|
|
30148
|
+
"expo-haptics": "~56.0.3",
|
|
30149
|
+
"expo-linking": "~56.0.11",
|
|
30150
|
+
"expo-network": "~56.0.4",
|
|
30151
|
+
"expo-router": "~56.2.5",
|
|
30152
|
+
"expo-secure-store": "~56.0.4",
|
|
30153
|
+
"expo-status-bar": "~56.0.4",
|
|
30154
|
+
"expo-web-browser": "~56.0.5",
|
|
30155
|
+
"heroui-native": "^1.0.3",
|
|
30156
|
+
"react": "19.2.3",
|
|
30157
|
+
"react-dom": "19.2.3",
|
|
30158
|
+
"react-native": "0.85.3",
|
|
30159
|
+
"react-native-gesture-handler": "~2.31.1",
|
|
30160
|
+
"react-native-keyboard-controller": "1.21.6",
|
|
30161
|
+
"react-native-reanimated": "4.3.1",
|
|
30162
|
+
"react-native-safe-area-context": "~5.7.0",
|
|
30163
|
+
"react-native-screens": "4.25.2",
|
|
30164
|
+
"react-native-svg": "15.15.4",
|
|
29138
30165
|
"react-native-web": "~0.21.0",
|
|
29139
|
-
"react-native-worklets": "0.
|
|
29140
|
-
"tailwind-merge": "^3.
|
|
30166
|
+
"react-native-worklets": "0.8.3",
|
|
30167
|
+
"tailwind-merge": "^3.6.0",
|
|
29141
30168
|
"tailwind-variants": "^3.2.2",
|
|
29142
|
-
"tailwindcss": "^4.
|
|
29143
|
-
"uniwind": "^1.
|
|
30169
|
+
"tailwindcss": "^4.3.0",
|
|
30170
|
+
"uniwind": "^1.7.0"
|
|
29144
30171
|
},
|
|
29145
30172
|
"devDependencies": {
|
|
29146
|
-
"@types/node": "^
|
|
29147
|
-
"@types/react": "~19.2.
|
|
29148
|
-
"typescript": "
|
|
30173
|
+
"@types/node": "^25.9.1",
|
|
30174
|
+
"@types/react": "~19.2.14",
|
|
30175
|
+
"typescript": "^6"
|
|
29149
30176
|
}
|
|
29150
30177
|
}
|
|
29151
30178
|
`],
|
|
@@ -29165,6 +30192,8 @@ module.exports = uniwindConfig;
|
|
|
29165
30192
|
]
|
|
29166
30193
|
}`],
|
|
29167
30194
|
["frontend/native/uniwind/uniwind-env.d.ts", `/// <reference types="uniwind/types" />
|
|
30195
|
+
|
|
30196
|
+
declare module "*.css";
|
|
29168
30197
|
`],
|
|
29169
30198
|
["frontend/nuxt/_gitignore", `# Nuxt dev/build outputs
|
|
29170
30199
|
.output
|
|
@@ -29505,15 +30534,15 @@ initOpenNextCloudflareForDev();
|
|
|
29505
30534
|
"lucide-react": "^0.546.0",
|
|
29506
30535
|
"next": "^16.2.0",
|
|
29507
30536
|
"next-themes": "^0.4.6",
|
|
29508
|
-
"react": "^19.2.
|
|
29509
|
-
"react-dom": "^19.2.
|
|
30537
|
+
"react": "^19.2.6",
|
|
30538
|
+
"react-dom": "^19.2.6",
|
|
29510
30539
|
"sonner": "^2.0.5",
|
|
29511
30540
|
"babel-plugin-react-compiler": "^1.0.0"
|
|
29512
30541
|
},
|
|
29513
30542
|
"devDependencies": {
|
|
29514
30543
|
"@tailwindcss/postcss": "^4.1.18",
|
|
29515
30544
|
"@types/node": "^20",
|
|
29516
|
-
"@types/react": "^19.2.
|
|
30545
|
+
"@types/react": "^19.2.15",
|
|
29517
30546
|
"@types/react-dom": "^19.2.3",
|
|
29518
30547
|
"tailwindcss": "^4.1.18"
|
|
29519
30548
|
}
|
|
@@ -29910,8 +30939,8 @@ export function ThemeProvider({
|
|
|
29910
30939
|
"isbot": "^5.1.39",
|
|
29911
30940
|
"lucide-react": "^1.8.0",
|
|
29912
30941
|
"next-themes": "^0.4.6",
|
|
29913
|
-
"react": "^19.2.
|
|
29914
|
-
"react-dom": "^19.2.
|
|
30942
|
+
"react": "^19.2.6",
|
|
30943
|
+
"react-dom": "^19.2.6",
|
|
29915
30944
|
"react-router": "^7.14.1",
|
|
29916
30945
|
"sonner": "^2.0.7"
|
|
29917
30946
|
},
|
|
@@ -29919,7 +30948,7 @@ export function ThemeProvider({
|
|
|
29919
30948
|
"@react-router/dev": "^7.14.1",
|
|
29920
30949
|
"@tailwindcss/vite": "^4.2.2",
|
|
29921
30950
|
"@types/node": "^20",
|
|
29922
|
-
"@types/react": "^19.2.
|
|
30951
|
+
"@types/react": "^19.2.15",
|
|
29923
30952
|
"@types/react-dom": "^19.2.3",
|
|
29924
30953
|
"react-router-devtools": "^1.1.0",
|
|
29925
30954
|
"tailwindcss": "^4.2.2",
|
|
@@ -30085,13 +31114,7 @@ export default function App() {
|
|
|
30085
31114
|
{{else}}
|
|
30086
31115
|
export default function App() {
|
|
30087
31116
|
{{/if}}
|
|
30088
|
-
{{#if (eq auth "better-auth")}}
|
|
30089
|
-
const convex = new ConvexReactClient(env.VITE_CONVEX_URL, {
|
|
30090
|
-
expectAuth: true,
|
|
30091
|
-
});
|
|
30092
|
-
{{else}}
|
|
30093
31117
|
const convex = new ConvexReactClient(env.VITE_CONVEX_URL);
|
|
30094
|
-
{{/if}}
|
|
30095
31118
|
{{#if (eq auth "clerk")}}
|
|
30096
31119
|
return (
|
|
30097
31120
|
<ClerkProvider loaderData={loaderData}>
|
|
@@ -30453,15 +31476,15 @@ export default defineConfig({
|
|
|
30453
31476
|
"@tanstack/react-router": "^1.168.22",
|
|
30454
31477
|
"lucide-react": "^1.8.0",
|
|
30455
31478
|
"next-themes": "^0.4.6",
|
|
30456
|
-
"react": "^19.2.
|
|
30457
|
-
"react-dom": "^19.2.
|
|
31479
|
+
"react": "^19.2.6",
|
|
31480
|
+
"react-dom": "^19.2.6",
|
|
30458
31481
|
"sonner": "^2.0.7"
|
|
30459
31482
|
},
|
|
30460
31483
|
"devDependencies": {
|
|
30461
31484
|
"@tanstack/react-router-devtools": "^1.166.13",
|
|
30462
31485
|
"@tanstack/router-plugin": "^1.167.22",
|
|
30463
31486
|
"@types/node": "^22.13.14",
|
|
30464
|
-
"@types/react": "^19.2.
|
|
31487
|
+
"@types/react": "^19.2.15",
|
|
30465
31488
|
"@types/react-dom": "^19.2.3",
|
|
30466
31489
|
"@vitejs/plugin-react": "^6.0.1",
|
|
30467
31490
|
"postcss": "^8.5.10",
|
|
@@ -30545,13 +31568,7 @@ import { routeTree } from "./routeTree.gen";
|
|
|
30545
31568
|
{{else}}
|
|
30546
31569
|
import { ConvexProvider } from "convex/react";
|
|
30547
31570
|
{{/if}}
|
|
30548
|
-
{{#if (eq auth "better-auth")}}
|
|
30549
|
-
const convex = new ConvexReactClient(env.VITE_CONVEX_URL, {
|
|
30550
|
-
expectAuth: true,
|
|
30551
|
-
});
|
|
30552
|
-
{{else}}
|
|
30553
31571
|
const convex = new ConvexReactClient(env.VITE_CONVEX_URL);
|
|
30554
|
-
{{/if}}
|
|
30555
31572
|
{{/if}}
|
|
30556
31573
|
|
|
30557
31574
|
{{#if (and (eq auth "clerk") (ne backend "convex") (ne api "none"))}}
|
|
@@ -30904,8 +31921,8 @@ export default defineConfig({
|
|
|
30904
31921
|
"@tanstack/react-start": "^1.167.41",
|
|
30905
31922
|
"lucide-react": "^1.8.0",
|
|
30906
31923
|
"next-themes": "^0.4.6",
|
|
30907
|
-
"react": "^19.2.
|
|
30908
|
-
"react-dom": "^19.2.
|
|
31924
|
+
"react": "^19.2.6",
|
|
31925
|
+
"react-dom": "^19.2.6",
|
|
30909
31926
|
"sonner": "^2.0.7",
|
|
30910
31927
|
"tailwindcss": "^4.2.2"
|
|
30911
31928
|
},
|
|
@@ -30913,7 +31930,7 @@ export default defineConfig({
|
|
|
30913
31930
|
"@tanstack/react-router-devtools": "^1.166.13",
|
|
30914
31931
|
"@testing-library/dom": "^10.4.1",
|
|
30915
31932
|
"@testing-library/react": "^16.3.2",
|
|
30916
|
-
"@types/react": "^19.2.
|
|
31933
|
+
"@types/react": "^19.2.15",
|
|
30917
31934
|
"@types/react-dom": "^19.2.3",
|
|
30918
31935
|
"@vitejs/plugin-react": "^6.0.1",
|
|
30919
31936
|
"jsdom": "^29.0.2",
|
|
@@ -30933,12 +31950,10 @@ import { setupRouterSsrQueryIntegration } from "@tanstack/react-router-ssr-query
|
|
|
30933
31950
|
import { ConvexQueryClient } from "@convex-dev/react-query";
|
|
30934
31951
|
import { routeTree } from "./routeTree.gen";
|
|
30935
31952
|
import Loader from "./components/loader";
|
|
30936
|
-
import "./index.css";
|
|
30937
31953
|
import { env } from "@{{projectName}}/env/web";
|
|
30938
31954
|
{{else}}
|
|
30939
31955
|
import { createRouter as createTanStackRouter } from "@tanstack/react-router";
|
|
30940
31956
|
import Loader from "./components/loader";
|
|
30941
|
-
import "./index.css";
|
|
30942
31957
|
import { routeTree } from "./routeTree.gen";
|
|
30943
31958
|
{{#if (eq api "trpc")}}
|
|
30944
31959
|
import { QueryCache, QueryClient } from "@tanstack/react-query";
|
|
@@ -33106,14 +34121,14 @@ await app.finalize();
|
|
|
33106
34121
|
"clsx": "^2.1.1",
|
|
33107
34122
|
"lucide-react": "^0.546.0",
|
|
33108
34123
|
"next-themes": "^0.4.6",
|
|
33109
|
-
"react": "^19.2.
|
|
33110
|
-
"react-dom": "^19.2.
|
|
34124
|
+
"react": "^19.2.6",
|
|
34125
|
+
"react-dom": "^19.2.6",
|
|
33111
34126
|
"sonner": "^2.0.5",
|
|
33112
34127
|
"tailwind-merge": "^3.3.1",
|
|
33113
34128
|
"tw-animate-css": "^1.3.4"
|
|
33114
34129
|
},
|
|
33115
34130
|
"devDependencies": {
|
|
33116
|
-
"@types/react": "^19.2.
|
|
34131
|
+
"@types/react": "^19.2.15",
|
|
33117
34132
|
"@types/react-dom": "^19.2.3",
|
|
33118
34133
|
"tailwindcss": "^4.1.18"
|
|
33119
34134
|
},
|
|
@@ -33839,6 +34854,7 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
33839
34854
|
"compilerOptions": {
|
|
33840
34855
|
"jsx": "react-jsx",
|
|
33841
34856
|
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
34857
|
+
"types": [],
|
|
33842
34858
|
"paths": {
|
|
33843
34859
|
"@{{projectName}}/ui/*": ["./src/*"]
|
|
33844
34860
|
}
|
|
@@ -33846,6 +34862,71 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
33846
34862
|
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
|
33847
34863
|
"exclude": ["node_modules"]
|
|
33848
34864
|
}
|
|
34865
|
+
`],
|
|
34866
|
+
["payments/polar/convex/backend/convex/polar.ts.hbs", `import { Polar } from "@convex-dev/polar";
|
|
34867
|
+
|
|
34868
|
+
import { api, components } from "./_generated/api";
|
|
34869
|
+
import type { DataModel } from "./_generated/dataModel";
|
|
34870
|
+
import { action, query } from "./_generated/server";
|
|
34871
|
+
|
|
34872
|
+
type CurrentSubscription = Awaited<ReturnType<Polar<DataModel>["getCurrentSubscription"]>>;
|
|
34873
|
+
|
|
34874
|
+
export const polar: Polar<DataModel> = new Polar<DataModel>(components.polar, {
|
|
34875
|
+
getUserInfo: async (ctx) => {
|
|
34876
|
+
const user = await ctx.runQuery(api.auth.getCurrentUser);
|
|
34877
|
+
|
|
34878
|
+
if (!user) {
|
|
34879
|
+
throw new Error("Not authenticated");
|
|
34880
|
+
}
|
|
34881
|
+
|
|
34882
|
+
if (!user.email) {
|
|
34883
|
+
throw new Error("Authenticated user is missing an email address");
|
|
34884
|
+
}
|
|
34885
|
+
|
|
34886
|
+
return {
|
|
34887
|
+
userId: user._id,
|
|
34888
|
+
email: user.email,
|
|
34889
|
+
};
|
|
34890
|
+
},
|
|
34891
|
+
});
|
|
34892
|
+
|
|
34893
|
+
export const {
|
|
34894
|
+
changeCurrentSubscription,
|
|
34895
|
+
cancelCurrentSubscription,
|
|
34896
|
+
getConfiguredProducts,
|
|
34897
|
+
listAllProducts,
|
|
34898
|
+
listAllSubscriptions,
|
|
34899
|
+
generateCheckoutLink,
|
|
34900
|
+
generateCustomerPortalUrl,
|
|
34901
|
+
} = polar.api();
|
|
34902
|
+
|
|
34903
|
+
export const getCurrentSubscription = query({
|
|
34904
|
+
args: {},
|
|
34905
|
+
handler: async (ctx): Promise<CurrentSubscription | null> => {
|
|
34906
|
+
const user = await ctx.runQuery(api.auth.getCurrentUser);
|
|
34907
|
+
|
|
34908
|
+
if (!user) {
|
|
34909
|
+
return null;
|
|
34910
|
+
}
|
|
34911
|
+
|
|
34912
|
+
return await polar.getCurrentSubscription(ctx, {
|
|
34913
|
+
userId: user._id,
|
|
34914
|
+
});
|
|
34915
|
+
},
|
|
34916
|
+
});
|
|
34917
|
+
|
|
34918
|
+
export const syncProducts = action({
|
|
34919
|
+
args: {},
|
|
34920
|
+
handler: async (ctx): Promise<void> => {
|
|
34921
|
+
const user = await ctx.runQuery(api.auth.getCurrentUser);
|
|
34922
|
+
|
|
34923
|
+
if (!user) {
|
|
34924
|
+
throw new Error("Not authenticated");
|
|
34925
|
+
}
|
|
34926
|
+
|
|
34927
|
+
await polar.syncProducts(ctx);
|
|
34928
|
+
},
|
|
34929
|
+
});
|
|
33849
34930
|
`],
|
|
33850
34931
|
["payments/polar/server/base/src/lib/payments.ts.hbs", `import { Polar } from "@polar-sh/sdk";
|
|
33851
34932
|
{{#if (and (eq backend "self") (eq webDeploy "cloudflare") (includes frontend "svelte"))}}
|
|
@@ -34004,7 +35085,7 @@ function SuccessPage() {
|
|
|
34004
35085
|
</div>
|
|
34005
35086
|
`]
|
|
34006
35087
|
]);
|
|
34007
|
-
const TEMPLATE_COUNT =
|
|
35088
|
+
const TEMPLATE_COUNT = 475;
|
|
34008
35089
|
//#endregion
|
|
34009
35090
|
export { EMBEDDED_TEMPLATES, GeneratorError, Handlebars, TEMPLATE_COUNT, VirtualFileSystem, dependencyVersionMap, generate, generateReproducibleCommand, isBinaryFile, processAddonTemplates, processAddonsDeps, processFileContent, processTemplateString, transformFilename, writeBtsConfigToVfs };
|
|
34010
35091
|
|