@better-t-stack/template-generator 3.29.0 → 3.30.1
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 +6 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +989 -69
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
package/dist/index.mjs
CHANGED
|
@@ -775,6 +775,7 @@ const dependencyVersionMap = {
|
|
|
775
775
|
convex: "^1.33.1",
|
|
776
776
|
"@convex-dev/react-query": "^0.1.0",
|
|
777
777
|
"@convex-dev/agent": "^0.3.2",
|
|
778
|
+
"@convex-dev/polar": "^0.9.1",
|
|
778
779
|
"convex-svelte": "^0.0.12",
|
|
779
780
|
"convex-nuxt": "0.1.5",
|
|
780
781
|
"convex-vue": "^0.1.5",
|
|
@@ -808,7 +809,10 @@ const dependencyVersionMap = {
|
|
|
808
809
|
"@t3-oss/env-nextjs": "^0.13.1",
|
|
809
810
|
"@t3-oss/env-nuxt": "^0.13.1",
|
|
810
811
|
"@polar-sh/better-auth": "^1.8.4",
|
|
811
|
-
"@polar-sh/
|
|
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",
|
|
812
816
|
evlog: "^2.14.1"
|
|
813
817
|
};
|
|
814
818
|
/**
|
|
@@ -2210,7 +2214,7 @@ function buildNativeVars(frontend, backend, auth) {
|
|
|
2210
2214
|
});
|
|
2211
2215
|
return vars;
|
|
2212
2216
|
}
|
|
2213
|
-
function buildConvexBackendVars(frontend, auth, examples) {
|
|
2217
|
+
function buildConvexBackendVars(frontend, auth, payments, examples) {
|
|
2214
2218
|
const hasReactRouter = frontend.includes("react-router");
|
|
2215
2219
|
const hasTanStackRouter = frontend.includes("tanstack-router");
|
|
2216
2220
|
const hasNextJs = frontend.includes("next");
|
|
@@ -2255,9 +2259,25 @@ function buildConvexBackendVars(frontend, auth, examples) {
|
|
|
2255
2259
|
comment: "Web app URL for authentication (for Expo web support)"
|
|
2256
2260
|
});
|
|
2257
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
|
+
});
|
|
2258
2278
|
return vars;
|
|
2259
2279
|
}
|
|
2260
|
-
function buildConvexCommentBlocks(frontend, auth, examples) {
|
|
2280
|
+
function buildConvexCommentBlocks(frontend, auth, payments, examples) {
|
|
2261
2281
|
const needsConvexSiteUrl = frontend.includes("react-router") || frontend.includes("tanstack-router");
|
|
2262
2282
|
const hasNative = frontend.includes("native-bare") || frontend.includes("native-uniwind") || frontend.includes("native-unistyles");
|
|
2263
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");
|
|
@@ -2270,15 +2290,27 @@ function buildConvexCommentBlocks(frontend, auth, examples) {
|
|
|
2270
2290
|
if (auth === "better-auth") commentBlocks += `# Set Convex environment variables
|
|
2271
2291
|
# npx convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)
|
|
2272
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
|
+
`;
|
|
2273
2301
|
return commentBlocks;
|
|
2274
2302
|
}
|
|
2275
|
-
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) {
|
|
2276
2304
|
const hasReactRouter = frontend.includes("react-router");
|
|
2277
2305
|
const hasSvelte = frontend.includes("svelte");
|
|
2278
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");
|
|
2279
2309
|
let corsOrigin = "http://localhost:3001";
|
|
2280
2310
|
if (hasAstro) corsOrigin = "http://localhost:4321";
|
|
2281
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}`;
|
|
2282
2314
|
let databaseUrl = null;
|
|
2283
2315
|
if (database !== "none" && dbSetup === "none") switch (database) {
|
|
2284
2316
|
case "postgres":
|
|
@@ -2310,7 +2342,7 @@ function buildServerVars(backend, frontend, auth, api, database, dbSetup, runtim
|
|
|
2310
2342
|
},
|
|
2311
2343
|
{
|
|
2312
2344
|
key: "BETTER_AUTH_URL",
|
|
2313
|
-
value:
|
|
2345
|
+
value: betterAuthUrl,
|
|
2314
2346
|
condition: hasBetterAuth
|
|
2315
2347
|
},
|
|
2316
2348
|
{
|
|
@@ -2330,7 +2362,7 @@ function buildServerVars(backend, frontend, auth, api, database, dbSetup, runtim
|
|
|
2330
2362
|
},
|
|
2331
2363
|
{
|
|
2332
2364
|
key: "POLAR_SUCCESS_URL",
|
|
2333
|
-
value:
|
|
2365
|
+
value: polarSuccessUrl,
|
|
2334
2366
|
condition: payments === "polar"
|
|
2335
2367
|
},
|
|
2336
2368
|
{
|
|
@@ -2351,7 +2383,7 @@ function buildServerVars(backend, frontend, auth, api, database, dbSetup, runtim
|
|
|
2351
2383
|
];
|
|
2352
2384
|
}
|
|
2353
2385
|
function processEnvVariables(vfs, config) {
|
|
2354
|
-
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;
|
|
2355
2387
|
const hasReactRouter = frontend.includes("react-router");
|
|
2356
2388
|
const hasTanStackRouter = frontend.includes("tanstack-router");
|
|
2357
2389
|
const hasTanStackStart = frontend.includes("tanstack-start");
|
|
@@ -2372,13 +2404,13 @@ function processEnvVariables(vfs, config) {
|
|
|
2372
2404
|
const convexBackendDir = "packages/backend";
|
|
2373
2405
|
if (vfs.directoryExists(convexBackendDir)) {
|
|
2374
2406
|
const envLocalPath = `${convexBackendDir}/.env.local`;
|
|
2375
|
-
const commentBlocks = buildConvexCommentBlocks(frontend, auth, examples);
|
|
2407
|
+
const commentBlocks = buildConvexCommentBlocks(frontend, auth, payments, examples);
|
|
2376
2408
|
if (commentBlocks) {
|
|
2377
2409
|
let currentContent = "";
|
|
2378
2410
|
if (vfs.exists(envLocalPath)) currentContent = vfs.readFile(envLocalPath) || "";
|
|
2379
2411
|
vfs.writeFile(envLocalPath, commentBlocks + currentContent);
|
|
2380
2412
|
}
|
|
2381
|
-
const convexBackendVars = buildConvexBackendVars(frontend, auth, examples);
|
|
2413
|
+
const convexBackendVars = buildConvexBackendVars(frontend, auth, payments, examples);
|
|
2382
2414
|
if (convexBackendVars.length > 0) {
|
|
2383
2415
|
let existingContent = "";
|
|
2384
2416
|
if (vfs.exists(envLocalPath)) existingContent = vfs.readFile(envLocalPath) || "";
|
|
@@ -2388,7 +2420,7 @@ function processEnvVariables(vfs, config) {
|
|
|
2388
2420
|
}
|
|
2389
2421
|
return;
|
|
2390
2422
|
}
|
|
2391
|
-
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);
|
|
2392
2424
|
if (backend === "self") {
|
|
2393
2425
|
const webDir = "apps/web";
|
|
2394
2426
|
if (vfs.directoryExists(webDir)) writeEnvFile(vfs, `${webDir}/.env`, serverVars);
|
|
@@ -2609,11 +2641,37 @@ function getDeployTargets() {
|
|
|
2609
2641
|
//#endregion
|
|
2610
2642
|
//#region src/processors/payments-deps.ts
|
|
2611
2643
|
function processPaymentsDeps(vfs, config) {
|
|
2612
|
-
const { payments, frontend } = config;
|
|
2644
|
+
const { payments, frontend, backend } = config;
|
|
2613
2645
|
if (!payments || payments === "none") return;
|
|
2646
|
+
const backendPath = "packages/backend/package.json";
|
|
2614
2647
|
const authPath = "packages/auth/package.json";
|
|
2615
2648
|
const webPath = "apps/web/package.json";
|
|
2616
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
|
+
}
|
|
2617
2675
|
if (vfs.exists(authPath)) addPackageDependency({
|
|
2618
2676
|
vfs,
|
|
2619
2677
|
packagePath: authPath,
|
|
@@ -3728,7 +3786,6 @@ async function processAuthTemplates(vfs, templates, config) {
|
|
|
3728
3786
|
//#region src/template-handlers/payments.ts
|
|
3729
3787
|
async function processPaymentsTemplates(vfs, templates, config) {
|
|
3730
3788
|
if (!config.payments || config.payments === "none") return;
|
|
3731
|
-
if (config.backend === "convex") return;
|
|
3732
3789
|
const hasReactWeb = config.frontend.some((f) => [
|
|
3733
3790
|
"tanstack-router",
|
|
3734
3791
|
"react-router",
|
|
@@ -3738,7 +3795,10 @@ async function processPaymentsTemplates(vfs, templates, config) {
|
|
|
3738
3795
|
const hasNuxtWeb = config.frontend.includes("nuxt");
|
|
3739
3796
|
const hasSvelteWeb = config.frontend.includes("svelte");
|
|
3740
3797
|
const hasSolidWeb = config.frontend.includes("solid");
|
|
3741
|
-
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);
|
|
3742
3802
|
if (hasReactWeb) {
|
|
3743
3803
|
const reactFramework = config.frontend.find((f) => [
|
|
3744
3804
|
"tanstack-router",
|
|
@@ -6231,14 +6291,56 @@ export const getCurrentUser = query({
|
|
|
6231
6291
|
`],
|
|
6232
6292
|
["auth/better-auth/convex/backend/convex/http.ts.hbs", `import { httpRouter } from "convex/server";
|
|
6233
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}}
|
|
6234
6300
|
|
|
6235
6301
|
const http = httpRouter();
|
|
6236
6302
|
|
|
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
|
+
}),
|
|
6332
|
+
});
|
|
6333
|
+
|
|
6334
|
+
{{/if}}
|
|
6237
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"))}}
|
|
6238
6336
|
authComponent.registerRoutes(http, createAuth, { cors: true });
|
|
6239
6337
|
{{else}}
|
|
6240
6338
|
authComponent.registerRoutes(http, createAuth);
|
|
6241
6339
|
{{/if}}
|
|
6340
|
+
{{#if (eq payments "polar")}}
|
|
6341
|
+
|
|
6342
|
+
polar.registerRoutes(http);
|
|
6343
|
+
{{/if}}
|
|
6242
6344
|
|
|
6243
6345
|
export default http;
|
|
6244
6346
|
`],
|
|
@@ -7559,6 +7661,10 @@ export const { GET, POST } = handler;
|
|
|
7559
7661
|
import SignInForm from "@/components/sign-in-form";
|
|
7560
7662
|
import SignUpForm from "@/components/sign-up-form";
|
|
7561
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}}
|
|
7562
7668
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
7563
7669
|
import {
|
|
7564
7670
|
Authenticated,
|
|
@@ -7568,18 +7674,58 @@ import {
|
|
|
7568
7674
|
} from "convex/react";
|
|
7569
7675
|
import { useState } from "react";
|
|
7570
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
|
+
|
|
7571
7722
|
export default function DashboardPage() {
|
|
7572
7723
|
const [showSignIn, setShowSignIn] = useState(false);
|
|
7573
|
-
const privateData = useQuery(api.privateData.get);
|
|
7574
7724
|
|
|
7575
7725
|
return (
|
|
7576
7726
|
<>
|
|
7577
7727
|
<Authenticated>
|
|
7578
|
-
<
|
|
7579
|
-
<h1>Dashboard</h1>
|
|
7580
|
-
<p>privateData: {privateData?.message}</p>
|
|
7581
|
-
<UserMenu />
|
|
7582
|
-
</div>
|
|
7728
|
+
<DashboardContent />
|
|
7583
7729
|
</Authenticated>
|
|
7584
7730
|
<Unauthenticated>
|
|
7585
7731
|
{showSignIn ? (
|
|
@@ -8305,6 +8451,10 @@ export const authClient = createAuthClient({
|
|
|
8305
8451
|
["auth/better-auth/convex/web/react/react-router/src/routes/dashboard.tsx.hbs", `import SignInForm from "@/components/sign-in-form";
|
|
8306
8452
|
import SignUpForm from "@/components/sign-up-form";
|
|
8307
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}}
|
|
8308
8458
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
8309
8459
|
import {
|
|
8310
8460
|
Authenticated,
|
|
@@ -8316,11 +8466,44 @@ import { useState } from "react";
|
|
|
8316
8466
|
|
|
8317
8467
|
function PrivateDashboardContent() {
|
|
8318
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}}
|
|
8319
8476
|
|
|
8320
8477
|
return (
|
|
8321
8478
|
<div>
|
|
8322
8479
|
<h1>Dashboard</h1>
|
|
8323
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}}
|
|
8324
8507
|
<UserMenu />
|
|
8325
8508
|
</div>
|
|
8326
8509
|
);
|
|
@@ -8709,6 +8892,10 @@ export const authClient = createAuthClient({
|
|
|
8709
8892
|
["auth/better-auth/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs", `import SignInForm from "@/components/sign-in-form";
|
|
8710
8893
|
import SignUpForm from "@/components/sign-up-form";
|
|
8711
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}}
|
|
8712
8899
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
8713
8900
|
import { createFileRoute } from "@tanstack/react-router";
|
|
8714
8901
|
import {
|
|
@@ -8725,11 +8912,44 @@ export const Route = createFileRoute("/dashboard")({
|
|
|
8725
8912
|
|
|
8726
8913
|
function PrivateDashboardContent() {
|
|
8727
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}}
|
|
8728
8922
|
|
|
8729
8923
|
return (
|
|
8730
8924
|
<div>
|
|
8731
8925
|
<h1>Dashboard</h1>
|
|
8732
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}}
|
|
8733
8953
|
<UserMenu />
|
|
8734
8954
|
</div>
|
|
8735
8955
|
);
|
|
@@ -9133,6 +9353,10 @@ export const Route = createFileRoute("/api/auth/$")({
|
|
|
9133
9353
|
["auth/better-auth/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs", `import SignInForm from "@/components/sign-in-form";
|
|
9134
9354
|
import SignUpForm from "@/components/sign-up-form";
|
|
9135
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}}
|
|
9136
9360
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
9137
9361
|
import { createFileRoute } from "@tanstack/react-router";
|
|
9138
9362
|
import {
|
|
@@ -9147,18 +9371,58 @@ export const Route = createFileRoute("/dashboard")({
|
|
|
9147
9371
|
component: RouteComponent,
|
|
9148
9372
|
});
|
|
9149
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
|
+
|
|
9150
9419
|
function RouteComponent() {
|
|
9151
9420
|
const [showSignIn, setShowSignIn] = useState(false);
|
|
9152
|
-
const privateData = useQuery(api.privateData.get);
|
|
9153
9421
|
|
|
9154
9422
|
return (
|
|
9155
9423
|
<>
|
|
9156
9424
|
<Authenticated>
|
|
9157
|
-
<
|
|
9158
|
-
<h1>Dashboard</h1>
|
|
9159
|
-
<p>privateData: {privateData?.message}</p>
|
|
9160
|
-
<UserMenu />
|
|
9161
|
-
</div>
|
|
9425
|
+
<PrivateDashboardContent />
|
|
9162
9426
|
</Authenticated>
|
|
9163
9427
|
<Unauthenticated>
|
|
9164
9428
|
{showSignIn ? (
|
|
@@ -9321,11 +9585,16 @@ export const Route = createFileRoute('/api/auth/$')({
|
|
|
9321
9585
|
})
|
|
9322
9586
|
`],
|
|
9323
9587
|
["auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs", `import { Button, Column, Host, Text as ExpoUIText } from "@expo/ui";
|
|
9324
|
-
import { View, ScrollView, StyleSheet } from "react-native";
|
|
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,19 +9622,59 @@ 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
|
-
<Host style={styles.titleHost}
|
|
9362
|
-
<
|
|
9363
|
-
|
|
9364
|
-
|
|
9365
|
-
|
|
9366
|
-
|
|
9367
|
-
</ExpoUIText>
|
|
9368
|
-
</Column>
|
|
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>
|
|
9369
9678
|
</Host>
|
|
9370
9679
|
|
|
9371
9680
|
{session?.user ? (
|
|
@@ -9398,6 +9707,18 @@ return (
|
|
|
9398
9707
|
}}
|
|
9399
9708
|
/>
|
|
9400
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}}
|
|
9401
9722
|
</View>
|
|
9402
9723
|
) : null}
|
|
9403
9724
|
|
|
@@ -9479,7 +9800,8 @@ paddingTop: 28,
|
|
|
9479
9800
|
paddingBottom: 32,
|
|
9480
9801
|
},
|
|
9481
9802
|
titleHost: {
|
|
9482
|
-
alignSelf: "
|
|
9803
|
+
alignSelf: "stretch",
|
|
9804
|
+
height: 34,
|
|
9483
9805
|
marginBottom: 24,
|
|
9484
9806
|
},
|
|
9485
9807
|
userCard: {
|
|
@@ -9491,6 +9813,9 @@ borderRadius: 16,
|
|
|
9491
9813
|
userHeader: {
|
|
9492
9814
|
marginBottom: 8,
|
|
9493
9815
|
},
|
|
9816
|
+
paymentActions: {
|
|
9817
|
+
marginTop: 12,
|
|
9818
|
+
},
|
|
9494
9819
|
statusCard: {
|
|
9495
9820
|
marginBottom: 16,
|
|
9496
9821
|
padding: 16,
|
|
@@ -10038,9 +10363,41 @@ export const authClient = createAuthClient({
|
|
|
10038
10363
|
}),
|
|
10039
10364
|
],
|
|
10040
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}}
|
|
10041
10393
|
`],
|
|
10042
|
-
["auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs", `import { authClient } from "@/lib/auth-client";
|
|
10043
|
-
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}}
|
|
10044
10401
|
import { StyleSheet } from "react-native-unistyles";
|
|
10045
10402
|
|
|
10046
10403
|
import { Container } from "@/components/container";
|
|
@@ -10065,6 +10422,48 @@ export default function Home() {
|
|
|
10065
10422
|
const privateData = useQuery(trpc.privateData.queryOptions());
|
|
10066
10423
|
{{/if}}
|
|
10067
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}}
|
|
10068
10467
|
|
|
10069
10468
|
return (
|
|
10070
10469
|
<Container>
|
|
@@ -10095,6 +10494,22 @@ export default function Home() {
|
|
|
10095
10494
|
>
|
|
10096
10495
|
<Text style={styles.signOutButtonText}>Sign Out</Text>
|
|
10097
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}}
|
|
10098
10513
|
</View>
|
|
10099
10514
|
) : null}
|
|
10100
10515
|
{{#unless (eq api "none")}}
|
|
@@ -10187,6 +10602,32 @@ const styles = StyleSheet.create((theme) => ({
|
|
|
10187
10602
|
signOutButtonText: {
|
|
10188
10603
|
fontWeight: "500",
|
|
10189
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
|
+
},
|
|
10190
10631
|
apiStatusCard: {
|
|
10191
10632
|
marginBottom: 24,
|
|
10192
10633
|
borderRadius: 8,
|
|
@@ -10691,9 +11132,14 @@ const styles = StyleSheet.create((theme) => ({
|
|
|
10691
11132
|
},
|
|
10692
11133
|
}));
|
|
10693
11134
|
`],
|
|
10694
|
-
["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}}
|
|
10695
11141
|
import { Container } from "@/components/container";
|
|
10696
|
-
import { authClient } from "@/lib/auth-client";
|
|
11142
|
+
import { authClient{{#if (eq payments "polar")}}, polarNativeClient{{/if}} } from "@/lib/auth-client";
|
|
10697
11143
|
import { Ionicons } from "@expo/vector-icons";
|
|
10698
11144
|
import { Card, Chip, useThemeColor } from "heroui-native";
|
|
10699
11145
|
import { SignIn } from "@/components/sign-in";
|
|
@@ -10721,6 +11167,48 @@ const isConnected = healthCheck?.data === "OK";
|
|
|
10721
11167
|
const isLoading = healthCheck?.isLoading;
|
|
10722
11168
|
{{/if}}
|
|
10723
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}}
|
|
10724
11212
|
|
|
10725
11213
|
const mutedColor = useThemeColor("muted");
|
|
10726
11214
|
const successColor = useThemeColor("success");
|
|
@@ -10755,6 +11243,22 @@ return (
|
|
|
10755
11243
|
>
|
|
10756
11244
|
<Text className="text-foreground font-medium">Sign Out</Text>
|
|
10757
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}}
|
|
10758
11262
|
</Card>
|
|
10759
11263
|
) : null}
|
|
10760
11264
|
|
|
@@ -10813,7 +11317,8 @@ return (
|
|
|
10813
11317
|
)}
|
|
10814
11318
|
</Container>
|
|
10815
11319
|
);
|
|
10816
|
-
}
|
|
11320
|
+
}
|
|
11321
|
+
`],
|
|
10817
11322
|
["auth/better-auth/native/uniwind/components/sign-in.tsx.hbs", `import { authClient } from "@/lib/auth-client";
|
|
10818
11323
|
{{#if (eq api "trpc")}}
|
|
10819
11324
|
import { queryClient } from "@/utils/trpc";
|
|
@@ -12470,7 +12975,7 @@ import { authClient } from "../lib/auth-client";
|
|
|
12470
12975
|
`],
|
|
12471
12976
|
["auth/better-auth/web/astro/src/lib/auth-client.ts.hbs", `import { createAuthClient } from "better-auth/client";
|
|
12472
12977
|
{{#if (eq payments "polar")}}
|
|
12473
|
-
import { polarClient } from "@polar-sh/better-auth";
|
|
12978
|
+
import { polarClient } from "@polar-sh/better-auth/client";
|
|
12474
12979
|
{{/if}}
|
|
12475
12980
|
{{#if (ne backend "self")}}
|
|
12476
12981
|
import { PUBLIC_SERVER_URL } from "astro:env/client";
|
|
@@ -13006,7 +13511,7 @@ watchEffect(() => {
|
|
|
13006
13511
|
`],
|
|
13007
13512
|
["auth/better-auth/web/nuxt/app/plugins/auth-client.ts.hbs", `import { createAuthClient } from "better-auth/vue";
|
|
13008
13513
|
{{#if (eq payments "polar")}}
|
|
13009
|
-
import { polarClient } from "@polar-sh/better-auth";
|
|
13514
|
+
import { polarClient } from "@polar-sh/better-auth/client";
|
|
13010
13515
|
{{/if}}
|
|
13011
13516
|
|
|
13012
13517
|
export default defineNuxtPlugin(() => {
|
|
@@ -13032,7 +13537,7 @@ export default defineNuxtPlugin(() => {
|
|
|
13032
13537
|
`],
|
|
13033
13538
|
["auth/better-auth/web/react/base/src/lib/auth-client.ts.hbs", `import { createAuthClient } from "better-auth/react";
|
|
13034
13539
|
{{#if (eq payments "polar")}}
|
|
13035
|
-
import { polarClient } from "@polar-sh/better-auth";
|
|
13540
|
+
import { polarClient } from "@polar-sh/better-auth/client";
|
|
13036
13541
|
{{/if}}
|
|
13037
13542
|
{{#unless (eq backend "self")}}
|
|
13038
13543
|
import { env } from "@{{projectName}}/env/web";
|
|
@@ -15278,7 +15783,7 @@ export default function UserMenu() {
|
|
|
15278
15783
|
`],
|
|
15279
15784
|
["auth/better-auth/web/solid/src/lib/auth-client.ts.hbs", `import { createAuthClient } from "better-auth/solid";
|
|
15280
15785
|
{{#if (eq payments "polar")}}
|
|
15281
|
-
import { polarClient } from "@polar-sh/better-auth";
|
|
15786
|
+
import { polarClient } from "@polar-sh/better-auth/client";
|
|
15282
15787
|
{{/if}}
|
|
15283
15788
|
import { env } from "@{{projectName}}/env/web";
|
|
15284
15789
|
|
|
@@ -15696,7 +16201,7 @@ import { PUBLIC_SERVER_URL } from "$env/static/public";
|
|
|
15696
16201
|
{{/unless}}
|
|
15697
16202
|
import { createAuthClient } from "better-auth/svelte";
|
|
15698
16203
|
{{#if (eq payments "polar")}}
|
|
15699
|
-
import { polarClient } from "@polar-sh/better-auth";
|
|
16204
|
+
import { polarClient } from "@polar-sh/better-auth/client";
|
|
15700
16205
|
{{/if}}
|
|
15701
16206
|
|
|
15702
16207
|
export const authClient = createAuthClient({
|
|
@@ -17363,6 +17868,9 @@ export const httpAction = httpActionGeneric;
|
|
|
17363
17868
|
{{#if (eq auth "better-auth")}}
|
|
17364
17869
|
import betterAuth from "@convex-dev/better-auth/convex.config";
|
|
17365
17870
|
{{/if}}
|
|
17871
|
+
{{#if (eq payments "polar")}}
|
|
17872
|
+
import polar from "@convex-dev/polar/convex.config.js";
|
|
17873
|
+
{{/if}}
|
|
17366
17874
|
{{#if (includes examples "ai")}}
|
|
17367
17875
|
import agent from "@convex-dev/agent/convex.config";
|
|
17368
17876
|
{{/if}}
|
|
@@ -17371,6 +17879,9 @@ const app = defineApp();
|
|
|
17371
17879
|
{{#if (eq auth "better-auth")}}
|
|
17372
17880
|
app.use(betterAuth);
|
|
17373
17881
|
{{/if}}
|
|
17882
|
+
{{#if (eq payments "polar")}}
|
|
17883
|
+
app.use(polar);
|
|
17884
|
+
{{/if}}
|
|
17374
17885
|
{{#if (includes examples "ai")}}
|
|
17375
17886
|
app.use(agent);
|
|
17376
17887
|
{{/if}}
|
|
@@ -17694,6 +18205,32 @@ new Elysia()
|
|
|
17694
18205
|
{{/if}}
|
|
17695
18206
|
}),
|
|
17696
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}}
|
|
17697
18234
|
{{#if (eq auth "better-auth")}}
|
|
17698
18235
|
.all("/api/auth/*", async (context) => {
|
|
17699
18236
|
const { request, status } = context;
|
|
@@ -17816,6 +18353,31 @@ app.use(clerkMiddleware());
|
|
|
17816
18353
|
app.all("/api/auth{/*path}", toNodeHandler(auth));
|
|
17817
18354
|
{{/if}}
|
|
17818
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}}
|
|
17819
18381
|
{{#if (eq api "trpc")}}
|
|
17820
18382
|
app.use(
|
|
17821
18383
|
"/trpc",
|
|
@@ -17973,6 +18535,31 @@ fastify.register(clerkPlugin, {
|
|
|
17973
18535
|
});
|
|
17974
18536
|
{{/if}}
|
|
17975
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}}
|
|
17976
18563
|
{{#if (eq api "orpc")}}
|
|
17977
18564
|
fastify.register(async (rpcApp) => {
|
|
17978
18565
|
// Fully utilize oRPC features by letting oRPC parse the request body.
|
|
@@ -18147,6 +18734,29 @@ app.on(
|
|
|
18147
18734
|
);
|
|
18148
18735
|
{{/if}}
|
|
18149
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}}
|
|
18150
18760
|
{{#if (eq api "orpc")}}
|
|
18151
18761
|
export const apiHandler = new OpenAPIHandler(appRouter, {
|
|
18152
18762
|
plugins: [
|
|
@@ -26441,7 +27051,12 @@ const styles = StyleSheet.create({
|
|
|
26441
27051
|
});
|
|
26442
27052
|
`],
|
|
26443
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";
|
|
26444
|
-
import { View, ScrollView, StyleSheet } from "react-native";
|
|
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}}
|
|
26445
27060
|
import { Container } from "@/components/container";
|
|
26446
27061
|
import { useColorScheme } from "@/lib/use-color-scheme";
|
|
26447
27062
|
import { NAV_THEME } from "@/lib/constants";
|
|
@@ -26464,7 +27079,7 @@ import { router } from "expo-router";
|
|
|
26464
27079
|
import { useAuth, useUser } from "@clerk/expo";
|
|
26465
27080
|
import { SignOutButton } from "@/components/sign-out-button";
|
|
26466
27081
|
{{else if (and (eq backend "convex") (eq auth "better-auth"))}}
|
|
26467
|
-
import { useConvexAuth, useQuery } from "convex/react";
|
|
27082
|
+
import { {{#if (eq payments "polar")}}useAction, {{/if}}useConvexAuth, useQuery } from "convex/react";
|
|
26468
27083
|
import { api } from "@{{ projectName }}/backend/convex/_generated/api";
|
|
26469
27084
|
import { authClient } from "@/lib/auth-client";
|
|
26470
27085
|
import { SignIn } from "@/components/sign-in";
|
|
@@ -26494,22 +27109,71 @@ const { user } = useUser();
|
|
|
26494
27109
|
const healthCheck = useQuery(api.healthCheck.get);
|
|
26495
27110
|
const { isAuthenticated } = useConvexAuth();
|
|
26496
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}}
|
|
26497
27163
|
{{else if (eq backend "convex")}}
|
|
26498
27164
|
const healthCheck = useQuery(api.healthCheck.get);
|
|
26499
27165
|
{{/if}}
|
|
26500
27166
|
|
|
26501
27167
|
return (
|
|
26502
27168
|
<Container>
|
|
26503
|
-
<ScrollView style={styles.scrollView}>
|
|
27169
|
+
<ScrollView style={styles.scrollView} contentInsetAdjustmentBehavior="never">
|
|
26504
27170
|
<View style={styles.content}>
|
|
26505
|
-
<Host style={styles.titleHost}
|
|
26506
|
-
<
|
|
26507
|
-
|
|
26508
|
-
|
|
26509
|
-
|
|
26510
|
-
|
|
26511
|
-
</ExpoUIText>
|
|
26512
|
-
</Column>
|
|
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>
|
|
26513
27177
|
</Host>
|
|
26514
27178
|
|
|
26515
27179
|
{{#unless (and (eq backend "convex") (eq auth "better-auth"))}}
|
|
@@ -26703,6 +27367,21 @@ return (
|
|
|
26703
27367
|
}}
|
|
26704
27368
|
/>
|
|
26705
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}}
|
|
26706
27385
|
</View>
|
|
26707
27386
|
) : (
|
|
26708
27387
|
<>
|
|
@@ -26727,7 +27406,8 @@ paddingTop: 28,
|
|
|
26727
27406
|
paddingBottom: 32,
|
|
26728
27407
|
},
|
|
26729
27408
|
titleHost: {
|
|
26730
|
-
alignSelf: "
|
|
27409
|
+
alignSelf: "stretch",
|
|
27410
|
+
height: 34,
|
|
26731
27411
|
marginBottom: 24,
|
|
26732
27412
|
},
|
|
26733
27413
|
card: {
|
|
@@ -26757,6 +27437,9 @@ borderRadius: 16,
|
|
|
26757
27437
|
userHeader: {
|
|
26758
27438
|
marginBottom: 8,
|
|
26759
27439
|
},
|
|
27440
|
+
paymentActions: {
|
|
27441
|
+
marginTop: 12,
|
|
27442
|
+
},
|
|
26760
27443
|
authHost: {
|
|
26761
27444
|
marginBottom: 12,
|
|
26762
27445
|
},
|
|
@@ -26893,7 +27576,10 @@ export function Container({ children }: { children: React.ReactNode }) {
|
|
|
26893
27576
|
: NAV_THEME.light.background;
|
|
26894
27577
|
|
|
26895
27578
|
return (
|
|
26896
|
-
<SafeAreaView
|
|
27579
|
+
<SafeAreaView
|
|
27580
|
+
edges={["left", "right", "bottom"]}
|
|
27581
|
+
style={[styles.container, { backgroundColor }]}
|
|
27582
|
+
>
|
|
26897
27583
|
{children}
|
|
26898
27584
|
</SafeAreaView>
|
|
26899
27585
|
);
|
|
@@ -26904,7 +27590,6 @@ const styles = StyleSheet.create({
|
|
|
26904
27590
|
flex: 1,
|
|
26905
27591
|
},
|
|
26906
27592
|
});
|
|
26907
|
-
|
|
26908
27593
|
`],
|
|
26909
27594
|
["frontend/native/bare/components/header-button.tsx.hbs", `import FontAwesome from "@expo/vector-icons/FontAwesome";
|
|
26910
27595
|
import { forwardRef } from "react";
|
|
@@ -27050,7 +27735,7 @@ module.exports = config;
|
|
|
27050
27735
|
"react-native-gesture-handler": "~2.31.1",
|
|
27051
27736
|
"react-native-reanimated": "4.3.1",
|
|
27052
27737
|
"react-native-safe-area-context": "~5.7.0",
|
|
27053
|
-
"react-native-screens": "4.25.
|
|
27738
|
+
"react-native-screens": "4.25.2",
|
|
27054
27739
|
"react-native-web": "~0.21.0",
|
|
27055
27740
|
"react-native-worklets": "0.8.3"
|
|
27056
27741
|
},
|
|
@@ -27609,7 +28294,12 @@ const styles = StyleSheet.create((theme) => ({
|
|
|
27609
28294
|
},
|
|
27610
28295
|
}));
|
|
27611
28296
|
`],
|
|
27612
|
-
["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}}
|
|
27613
28303
|
import { StyleSheet } from "react-native-unistyles";
|
|
27614
28304
|
import { Container } from "@/components/container";
|
|
27615
28305
|
|
|
@@ -27632,7 +28322,7 @@ import { Link } from "expo-router";
|
|
|
27632
28322
|
import { useAuth, useUser } from "@clerk/expo";
|
|
27633
28323
|
import { SignOutButton } from "@/components/sign-out-button";
|
|
27634
28324
|
{{else if (and (eq backend "convex") (eq auth "better-auth"))}}
|
|
27635
|
-
import { useConvexAuth, useQuery } from "convex/react";
|
|
28325
|
+
import { {{#if (eq payments "polar")}}useAction, {{/if}}useConvexAuth, useQuery } from "convex/react";
|
|
27636
28326
|
import { api } from "@{{ projectName }}/backend/convex/_generated/api";
|
|
27637
28327
|
import { authClient } from "@/lib/auth-client";
|
|
27638
28328
|
import { SignIn } from "@/components/sign-in";
|
|
@@ -27660,6 +28350,57 @@ export default function Home() {
|
|
|
27660
28350
|
const healthCheck = useQuery(api.healthCheck.get);
|
|
27661
28351
|
const { isAuthenticated } = useConvexAuth();
|
|
27662
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}}
|
|
27663
28404
|
{{else if (eq backend "convex")}}
|
|
27664
28405
|
const healthCheck = useQuery(api.healthCheck.get);
|
|
27665
28406
|
{{/if}}
|
|
@@ -27802,6 +28543,25 @@ export default function Home() {
|
|
|
27802
28543
|
>
|
|
27803
28544
|
<Text style={styles.signOutText}>Sign Out</Text>
|
|
27804
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}}
|
|
27805
28565
|
</View>
|
|
27806
28566
|
) : null}
|
|
27807
28567
|
<View style={styles.apiStatusCard}>
|
|
@@ -27954,6 +28714,31 @@ const styles = StyleSheet.create((theme) => ({
|
|
|
27954
28714
|
color: theme.colors.destructiveForeground,
|
|
27955
28715
|
fontWeight: "500",
|
|
27956
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
|
+
},
|
|
27957
28742
|
apiStatusCard: {
|
|
27958
28743
|
marginBottom: theme.spacing.md,
|
|
27959
28744
|
borderRadius: theme.borderRadius.lg,
|
|
@@ -28253,7 +29038,7 @@ module.exports = config;
|
|
|
28253
29038
|
"react-native-nitro-modules": "^0.35.7",
|
|
28254
29039
|
"react-native-reanimated": "4.3.1",
|
|
28255
29040
|
"react-native-safe-area-context": "~5.7.0",
|
|
28256
|
-
"react-native-screens": "4.25.
|
|
29041
|
+
"react-native-screens": "4.25.2",
|
|
28257
29042
|
"react-native-unistyles": "^3.2.4",
|
|
28258
29043
|
"react-native-web": "~0.21.0",
|
|
28259
29044
|
"react-native-worklets": "0.8.3"
|
|
@@ -28805,7 +29590,12 @@ export default function TabTwo() {
|
|
|
28805
29590
|
);
|
|
28806
29591
|
}
|
|
28807
29592
|
`],
|
|
28808
|
-
["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}}
|
|
28809
29599
|
import { Container } from "@/components/container";
|
|
28810
29600
|
{{#if (eq api "orpc")}}
|
|
28811
29601
|
import { useQuery } from "@tanstack/react-query";
|
|
@@ -28826,7 +29616,7 @@ import { Link } from "expo-router";
|
|
|
28826
29616
|
import { useAuth, useUser } from "@clerk/expo";
|
|
28827
29617
|
import { SignOutButton } from "@/components/sign-out-button";
|
|
28828
29618
|
{{else if (and (eq backend "convex") (eq auth "better-auth"))}}
|
|
28829
|
-
import { useConvexAuth, useQuery } from "convex/react";
|
|
29619
|
+
import { {{#if (eq payments "polar")}}useAction, {{/if}}useConvexAuth, useQuery } from "convex/react";
|
|
28830
29620
|
import { api } from "@{{projectName}}/backend/convex/_generated/api";
|
|
28831
29621
|
import { authClient } from "@/lib/auth-client";
|
|
28832
29622
|
import { SignIn } from "@/components/sign-in";
|
|
@@ -28858,6 +29648,57 @@ const { user } = useUser();
|
|
|
28858
29648
|
const healthCheck = useQuery(api.healthCheck.get);
|
|
28859
29649
|
const { isAuthenticated } = useConvexAuth();
|
|
28860
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}}
|
|
28861
29702
|
{{else if (eq backend "convex")}}
|
|
28862
29703
|
const healthCheck = useQuery(api.healthCheck.get);
|
|
28863
29704
|
{{/if}}
|
|
@@ -29009,6 +29850,19 @@ return (
|
|
|
29009
29850
|
Sign Out
|
|
29010
29851
|
</Button>
|
|
29011
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}}
|
|
29012
29866
|
</Surface>
|
|
29013
29867
|
) : null}
|
|
29014
29868
|
<Surface variant="secondary" className="p-4 rounded-xl">
|
|
@@ -29306,7 +30160,7 @@ module.exports = uniwindConfig;
|
|
|
29306
30160
|
"react-native-keyboard-controller": "1.21.6",
|
|
29307
30161
|
"react-native-reanimated": "4.3.1",
|
|
29308
30162
|
"react-native-safe-area-context": "~5.7.0",
|
|
29309
|
-
"react-native-screens": "4.25.
|
|
30163
|
+
"react-native-screens": "4.25.2",
|
|
29310
30164
|
"react-native-svg": "15.15.4",
|
|
29311
30165
|
"react-native-web": "~0.21.0",
|
|
29312
30166
|
"react-native-worklets": "0.8.3",
|
|
@@ -34000,6 +34854,7 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
34000
34854
|
"compilerOptions": {
|
|
34001
34855
|
"jsx": "react-jsx",
|
|
34002
34856
|
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
|
34857
|
+
"types": [],
|
|
34003
34858
|
"paths": {
|
|
34004
34859
|
"@{{projectName}}/ui/*": ["./src/*"]
|
|
34005
34860
|
}
|
|
@@ -34007,6 +34862,71 @@ export function cn(...inputs: ClassValue[]) {
|
|
|
34007
34862
|
"include": ["src/**/*.ts", "src/**/*.tsx"],
|
|
34008
34863
|
"exclude": ["node_modules"]
|
|
34009
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
|
+
});
|
|
34010
34930
|
`],
|
|
34011
34931
|
["payments/polar/server/base/src/lib/payments.ts.hbs", `import { Polar } from "@polar-sh/sdk";
|
|
34012
34932
|
{{#if (and (eq backend "self") (eq webDeploy "cloudflare") (includes frontend "svelte"))}}
|
|
@@ -34165,7 +35085,7 @@ function SuccessPage() {
|
|
|
34165
35085
|
</div>
|
|
34166
35086
|
`]
|
|
34167
35087
|
]);
|
|
34168
|
-
const TEMPLATE_COUNT =
|
|
35088
|
+
const TEMPLATE_COUNT = 475;
|
|
34169
35089
|
//#endregion
|
|
34170
35090
|
export { EMBEDDED_TEMPLATES, GeneratorError, Handlebars, TEMPLATE_COUNT, VirtualFileSystem, dependencyVersionMap, generate, generateReproducibleCommand, isBinaryFile, processAddonTemplates, processAddonsDeps, processFileContent, processTemplateString, transformFilename, writeBtsConfigToVfs };
|
|
34171
35091
|
|