create-better-t-stack 2.45.5 → 2.46.0-canary.85c43fef

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/index.d.ts +10 -0
  3. package/dist/index.js +1 -1
  4. package/dist/{src-Cun9EO6e.js → src-lN80CwOs.js} +396 -162
  5. package/package.json +1 -1
  6. package/templates/auth/better-auth/convex/backend/convex/auth.config.ts.hbs +8 -0
  7. package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +48 -0
  8. package/templates/auth/better-auth/convex/backend/convex/convex.config.ts.hbs +7 -0
  9. package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +12 -0
  10. package/templates/auth/better-auth/convex/backend/convex/privateData.ts.hbs +16 -0
  11. package/templates/auth/better-auth/convex/web/react/next/src/app/api/auth/[...all]/route.ts.hbs +3 -0
  12. package/templates/auth/better-auth/convex/web/react/next/src/app/dashboard/page.tsx.hbs +40 -0
  13. package/templates/auth/better-auth/convex/web/react/next/src/components/sign-in-form.tsx.hbs +129 -0
  14. package/templates/auth/better-auth/convex/web/react/next/src/components/sign-up-form.tsx.hbs +154 -0
  15. package/templates/auth/better-auth/convex/web/react/next/src/components/user-menu.tsx.hbs +48 -0
  16. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-client.ts.hbs +6 -0
  17. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-server.ts.hbs +6 -0
  18. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/lib/auth-client.ts.hbs +10 -0
  19. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs +43 -0
  20. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/sign-in-form.tsx.hbs +133 -0
  21. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/sign-up-form.tsx.hbs +158 -0
  22. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/user-menu.tsx.hbs +50 -0
  23. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-client.ts.hbs +6 -0
  24. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-server.ts.hbs +5 -0
  25. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/api/auth/$.ts.hbs +11 -0
  26. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs +43 -0
  27. package/templates/auth/better-auth/server/base/src/lib/auth.ts.hbs +153 -6
  28. package/templates/auth/better-auth/web/nuxt/app/pages/dashboard.vue.hbs +36 -2
  29. package/templates/auth/better-auth/web/nuxt/app/plugins/auth-client.ts.hbs +22 -0
  30. package/templates/auth/better-auth/web/react/base/src/lib/auth-client.ts.hbs +6 -0
  31. package/templates/auth/better-auth/web/react/next/src/app/dashboard/dashboard.tsx.hbs +58 -0
  32. package/templates/auth/better-auth/web/react/next/src/app/dashboard/page.tsx.hbs +31 -41
  33. package/templates/auth/better-auth/web/react/react-router/src/routes/dashboard.tsx.hbs +37 -3
  34. package/templates/auth/better-auth/web/react/tanstack-router/src/routes/dashboard.tsx.hbs +50 -32
  35. package/templates/auth/better-auth/web/react/tanstack-start/src/routes/dashboard.tsx.hbs +51 -36
  36. package/templates/auth/better-auth/web/solid/src/lib/auth-client.ts.hbs +11 -0
  37. package/templates/auth/better-auth/web/solid/src/routes/dashboard.tsx.hbs +52 -29
  38. package/templates/auth/better-auth/web/svelte/src/lib/{auth-client.ts → auth-client.ts.hbs} +6 -0
  39. package/templates/auth/better-auth/web/svelte/src/routes/dashboard/+page.svelte.hbs +24 -3
  40. package/templates/backend/server/server-base/package.json.hbs +1 -1
  41. package/templates/extras/bunfig.toml.hbs +4 -0
  42. package/templates/frontend/react/next/src/components/providers.tsx.hbs +8 -0
  43. package/templates/frontend/react/tanstack-router/src/main.tsx.hbs +8 -1
  44. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +51 -0
  45. package/templates/frontend/react/tanstack-start/src/routes/index.tsx.hbs +2 -2
  46. package/templates/frontend/react/web-base/src/components/header.tsx.hbs +2 -2
  47. package/templates/frontend/solid/package.json.hbs +10 -10
  48. package/templates/payments/polar/server/base/src/lib/payments.ts.hbs +6 -0
  49. package/templates/payments/polar/web/nuxt/app/pages/success.vue.hbs +11 -0
  50. package/templates/payments/polar/web/react/next/src/app/success/page.tsx.hbs +15 -0
  51. package/templates/payments/polar/web/react/react-router/src/routes/success.tsx.hbs +13 -0
  52. package/templates/payments/polar/web/react/tanstack-router/src/routes/success.tsx.hbs +19 -0
  53. package/templates/payments/polar/web/react/tanstack-start/src/routes/success.tsx.hbs +19 -0
  54. package/templates/payments/polar/web/solid/src/routes/success.tsx.hbs +23 -0
  55. package/templates/payments/polar/web/svelte/src/routes/success/+page.svelte.hbs +12 -0
  56. package/templates/auth/better-auth/web/nuxt/app/plugins/auth-client.ts +0 -16
  57. package/templates/auth/better-auth/web/react/next/src/components/theme-provider.tsx +0 -11
  58. package/templates/auth/better-auth/web/solid/src/lib/auth-client.ts +0 -5
  59. /package/templates/auth/better-auth/web/nuxt/app/components/{SignInForm.vue → SignInForm.vue.hbs} +0 -0
  60. /package/templates/auth/better-auth/web/nuxt/app/components/{SignUpForm.vue → SignUpForm.vue.hbs} +0 -0
  61. /package/templates/auth/better-auth/web/nuxt/app/components/{UserMenu.vue → UserMenu.vue.hbs} +0 -0
  62. /package/templates/auth/better-auth/web/nuxt/app/pages/{login.vue → login.vue.hbs} +0 -0
  63. /package/templates/auth/better-auth/web/react/next/src/app/login/{page.tsx → page.tsx.hbs} +0 -0
  64. /package/templates/auth/better-auth/web/react/next/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
  65. /package/templates/auth/better-auth/web/react/next/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
  66. /package/templates/auth/better-auth/web/react/next/src/components/{user-menu.tsx → user-menu.tsx.hbs} +0 -0
  67. /package/templates/auth/better-auth/web/react/react-router/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
  68. /package/templates/auth/better-auth/web/react/react-router/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
  69. /package/templates/auth/better-auth/web/react/react-router/src/components/{user-menu.tsx → user-menu.tsx.hbs} +0 -0
  70. /package/templates/auth/better-auth/web/react/react-router/src/routes/{login.tsx → login.tsx.hbs} +0 -0
  71. /package/templates/auth/better-auth/web/svelte/src/components/{SignInForm.svelte → SignInForm.svelte.hbs} +0 -0
  72. /package/templates/auth/better-auth/web/svelte/src/components/{SignUpForm.svelte → SignUpForm.svelte.hbs} +0 -0
  73. /package/templates/auth/better-auth/web/svelte/src/components/{UserMenu.svelte → UserMenu.svelte.hbs} +0 -0
  74. /package/templates/auth/better-auth/web/svelte/src/routes/login/{+page.svelte → +page.svelte.hbs} +0 -0
  75. /package/templates/frontend/react/web-base/src/components/{loader.tsx → loader.tsx.hbs} +0 -0
@@ -0,0 +1,158 @@
1
+ import { authClient } from "@/lib/auth-client";
2
+ import { useForm } from "@tanstack/react-form";
3
+ import { useNavigate } from "@tanstack/react-router";
4
+ import { toast } from "sonner";
5
+ import z from "zod";
6
+ import { Button } from "./ui/button";
7
+ import { Input } from "./ui/input";
8
+ import { Label } from "./ui/label";
9
+
10
+ export default function SignUpForm({
11
+ onSwitchToSignIn,
12
+ }: {
13
+ onSwitchToSignIn: () => void;
14
+ }) {
15
+ const navigate = useNavigate({
16
+ from: "/",
17
+ });
18
+
19
+ const form = useForm({
20
+ defaultValues: {
21
+ email: "",
22
+ password: "",
23
+ name: "",
24
+ },
25
+ onSubmit: async ({ value }) => {
26
+ await authClient.signUp.email(
27
+ {
28
+ email: value.email,
29
+ password: value.password,
30
+ name: value.name,
31
+ },
32
+ {
33
+ onSuccess: () => {
34
+ navigate({
35
+ to: "/dashboard",
36
+ });
37
+ toast.success("Sign up successful");
38
+ },
39
+ onError: (error) => {
40
+ toast.error(error.error.message || error.error.statusText);
41
+ },
42
+ },
43
+ );
44
+ },
45
+ validators: {
46
+ onSubmit: z.object({
47
+ name: z.string().min(2, "Name must be at least 2 characters"),
48
+ email: z.email("Invalid email address"),
49
+ password: z.string().min(8, "Password must be at least 8 characters"),
50
+ }),
51
+ },
52
+ });
53
+
54
+ return (
55
+ <div className="mx-auto w-full mt-10 max-w-md p-6">
56
+ <h1 className="mb-6 text-center text-3xl font-bold">Create Account</h1>
57
+
58
+ <form
59
+ onSubmit={(e) => {
60
+ e.preventDefault();
61
+ e.stopPropagation();
62
+ form.handleSubmit();
63
+ }}
64
+ className="space-y-4"
65
+ >
66
+ <div>
67
+ <form.Field name="name">
68
+ {(field) => (
69
+ <div className="space-y-2">
70
+ <Label htmlFor={field.name}>Name</Label>
71
+ <Input
72
+ id={field.name}
73
+ name={field.name}
74
+ value={field.state.value}
75
+ onBlur={field.handleBlur}
76
+ onChange={(e) => field.handleChange(e.target.value)}
77
+ />
78
+ {field.state.meta.errors.map((error) => (
79
+ <p key={error?.message} className="text-red-500">
80
+ {error?.message}
81
+ </p>
82
+ ))}
83
+ </div>
84
+ )}
85
+ </form.Field>
86
+ </div>
87
+
88
+ <div>
89
+ <form.Field name="email">
90
+ {(field) => (
91
+ <div className="space-y-2">
92
+ <Label htmlFor={field.name}>Email</Label>
93
+ <Input
94
+ id={field.name}
95
+ name={field.name}
96
+ type="email"
97
+ value={field.state.value}
98
+ onBlur={field.handleBlur}
99
+ onChange={(e) => field.handleChange(e.target.value)}
100
+ />
101
+ {field.state.meta.errors.map((error) => (
102
+ <p key={error?.message} className="text-red-500">
103
+ {error?.message}
104
+ </p>
105
+ ))}
106
+ </div>
107
+ )}
108
+ </form.Field>
109
+ </div>
110
+
111
+ <div>
112
+ <form.Field name="password">
113
+ {(field) => (
114
+ <div className="space-y-2">
115
+ <Label htmlFor={field.name}>Password</Label>
116
+ <Input
117
+ id={field.name}
118
+ name={field.name}
119
+ type="password"
120
+ value={field.state.value}
121
+ onBlur={field.handleBlur}
122
+ onChange={(e) => field.handleChange(e.target.value)}
123
+ />
124
+ {field.state.meta.errors.map((error) => (
125
+ <p key={error?.message} className="text-red-500">
126
+ {error?.message}
127
+ </p>
128
+ ))}
129
+ </div>
130
+ )}
131
+ </form.Field>
132
+ </div>
133
+
134
+ <form.Subscribe>
135
+ {(state) => (
136
+ <Button
137
+ type="submit"
138
+ className="w-full"
139
+ disabled={!state.canSubmit || state.isSubmitting}
140
+ >
141
+ {state.isSubmitting ? "Submitting..." : "Sign Up"}
142
+ </Button>
143
+ )}
144
+ </form.Subscribe>
145
+ </form>
146
+
147
+ <div className="mt-4 text-center">
148
+ <Button
149
+ variant="link"
150
+ onClick={onSwitchToSignIn}
151
+ className="text-indigo-600 hover:text-indigo-800"
152
+ >
153
+ Already have an account? Sign In
154
+ </Button>
155
+ </div>
156
+ </div>
157
+ );
158
+ }
@@ -0,0 +1,50 @@
1
+ import {
2
+ DropdownMenu,
3
+ DropdownMenuContent,
4
+ DropdownMenuItem,
5
+ DropdownMenuLabel,
6
+ DropdownMenuSeparator,
7
+ DropdownMenuTrigger,
8
+ } from "@/components/ui/dropdown-menu";
9
+ import { authClient } from "@/lib/auth-client";
10
+ import { useNavigate } from "@tanstack/react-router";
11
+ import { Button } from "./ui/button";
12
+ import { useQuery } from "convex/react";
13
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
14
+
15
+ export default function UserMenu() {
16
+ const navigate = useNavigate();
17
+ const user = useQuery(api.auth.getCurrentUser)
18
+
19
+ return (
20
+ <DropdownMenu>
21
+ <DropdownMenuTrigger asChild>
22
+ <Button variant="outline">{user?.name}</Button>
23
+ </DropdownMenuTrigger>
24
+ <DropdownMenuContent className="bg-card">
25
+ <DropdownMenuLabel>My Account</DropdownMenuLabel>
26
+ <DropdownMenuSeparator />
27
+ <DropdownMenuItem>{user?.email}</DropdownMenuItem>
28
+ <DropdownMenuItem asChild>
29
+ <Button
30
+ variant="destructive"
31
+ className="w-full"
32
+ onClick={() => {
33
+ authClient.signOut({
34
+ fetchOptions: {
35
+ onSuccess: () => {
36
+ navigate({
37
+ to: "/dashboard",
38
+ });
39
+ },
40
+ },
41
+ });
42
+ }}
43
+ >
44
+ Sign Out
45
+ </Button>
46
+ </DropdownMenuItem>
47
+ </DropdownMenuContent>
48
+ </DropdownMenu>
49
+ );
50
+ }
@@ -0,0 +1,6 @@
1
+ import { createAuthClient } from "better-auth/react";
2
+ import { convexClient } from "@convex-dev/better-auth/client/plugins";
3
+
4
+ export const authClient = createAuthClient({
5
+ plugins: [convexClient()],
6
+ });
@@ -0,0 +1,5 @@
1
+ import { createAuth } from "convex/auth";
2
+ import { setupFetchClient } from "@convex-dev/better-auth/react-start";
3
+
4
+ export const { fetchQuery, fetchMutation, fetchAction } =
5
+ setupFetchClient(createAuth);
@@ -0,0 +1,11 @@
1
+ import { reactStartHandler } from '@convex-dev/better-auth/react-start'
2
+ import { createServerFileRoute } from '@tanstack/react-start/server'
3
+
4
+ export const ServerRoute = createServerFileRoute('/api/auth/$').methods({
5
+ GET: ({ request }) => {
6
+ return reactStartHandler(request)
7
+ },
8
+ POST: ({ request }) => {
9
+ return reactStartHandler(request)
10
+ },
11
+ })
@@ -0,0 +1,43 @@
1
+ import SignInForm from "@/components/sign-in-form";
2
+ import SignUpForm from "@/components/sign-up-form";
3
+ import UserMenu from "@/components/user-menu";
4
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
5
+ import { createFileRoute } from "@tanstack/react-router";
6
+ import {
7
+ Authenticated,
8
+ AuthLoading,
9
+ Unauthenticated,
10
+ useQuery,
11
+ } from "convex/react";
12
+ import { useState } from "react";
13
+
14
+ export const Route = createFileRoute("/dashboard")({
15
+ component: RouteComponent,
16
+ });
17
+
18
+ function RouteComponent() {
19
+ const [showSignIn, setShowSignIn] = useState(false);
20
+ const privateData = useQuery(api.privateData.get);
21
+
22
+ return (
23
+ <>
24
+ <Authenticated>
25
+ <div>
26
+ <h1>Dashboard</h1>
27
+ <p>privateData: {privateData?.message}</p>
28
+ <UserMenu />
29
+ </div>
30
+ </Authenticated>
31
+ <Unauthenticated>
32
+ {showSignIn ? (
33
+ <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />
34
+ ) : (
35
+ <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />
36
+ )}
37
+ </Unauthenticated>
38
+ <AuthLoading>
39
+ <div>Loading...</div>
40
+ </AuthLoading>
41
+ </>
42
+ );
43
+ }
@@ -4,6 +4,10 @@ import { prismaAdapter } from "better-auth/adapters/prisma";
4
4
  {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
5
5
  import { expo } from "@better-auth/expo";
6
6
  {{/if}}
7
+ {{#if (eq payments "polar")}}
8
+ import { polar, checkout, portal } from "@polar-sh/better-auth";
9
+ import { polarClient } from "./payments";
10
+ {{/if}}
7
11
  import prisma from "../db";
8
12
 
9
13
  export const auth = betterAuth<BetterAuthOptions>({
@@ -28,9 +32,35 @@ export const auth = betterAuth<BetterAuthOptions>({
28
32
  secure: true,
29
33
  httpOnly: true,
30
34
  },
31
- }
35
+ },
36
+ {{#if (eq payments "polar")}}
37
+ plugins: [
38
+ polar({
39
+ client: polarClient,
40
+ createCustomerOnSignUp: true,
41
+ enableCustomerPortal: true,
42
+ use: [
43
+ checkout({
44
+ products: [
45
+ {
46
+ productId: "your-product-id",
47
+ slug: "pro",
48
+ },
49
+ ],
50
+ successUrl: process.env.POLAR_SUCCESS_URL,
51
+ authenticatedUsersOnly: true,
52
+ }),
53
+ portal(),
54
+ ],
55
+ }),
56
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
57
+ expo(),
58
+ {{/if}}
59
+ ],
60
+ {{else}}
32
61
  {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
33
- , plugins: [expo()]
62
+ plugins: [expo()],
63
+ {{/if}}
34
64
  {{/if}}
35
65
  });
36
66
  {{/if}}
@@ -42,6 +72,10 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle";
42
72
  {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
43
73
  import { expo } from "@better-auth/expo";
44
74
  {{/if}}
75
+ {{#if (eq payments "polar")}}
76
+ import { polar, checkout, portal } from "@polar-sh/better-auth";
77
+ import { polarClient } from "./payments";
78
+ {{/if}}
45
79
  import { db } from "../db";
46
80
  import * as schema from "../db/schema/auth";
47
81
 
@@ -68,9 +102,35 @@ export const auth = betterAuth<BetterAuthOptions>({
68
102
  httpOnly: true,
69
103
  },
70
104
  },
105
+ {{#if (eq payments "polar")}}
106
+ plugins: [
107
+ polar({
108
+ client: polarClient,
109
+ createCustomerOnSignUp: true,
110
+ enableCustomerPortal: true,
111
+ use: [
112
+ checkout({
113
+ products: [
114
+ {
115
+ productId: "your-product-id",
116
+ slug: "pro",
117
+ },
118
+ ],
119
+ successUrl: process.env.POLAR_SUCCESS_URL,
120
+ authenticatedUsersOnly: true,
121
+ }),
122
+ portal(),
123
+ ],
124
+ }),
125
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
126
+ expo(),
127
+ {{/if}}
128
+ ],
129
+ {{else}}
71
130
  {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
72
131
  plugins: [expo()],
73
132
  {{/if}}
133
+ {{/if}}
74
134
  });
75
135
  {{/if}}
76
136
 
@@ -80,6 +140,10 @@ import { drizzleAdapter } from "better-auth/adapters/drizzle";
80
140
  {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
81
141
  import { expo } from "@better-auth/expo";
82
142
  {{/if}}
143
+ {{#if (eq payments "polar")}}
144
+ import { polar, checkout, portal } from "@polar-sh/better-auth";
145
+ import { polarClient } from "./payments";
146
+ {{/if}}
83
147
  import { db } from "../db";
84
148
  import * as schema from "../db/schema/auth";
85
149
  import { env } from "cloudflare:workers";
@@ -109,9 +173,32 @@ export const auth = betterAuth<BetterAuthOptions>({
109
173
  httpOnly: true,
110
174
  },
111
175
  },
176
+ {{#if (eq payments "polar")}}
177
+ plugins: [
178
+ polar({
179
+ client: polarClient,
180
+ createCustomerOnSignUp: true,
181
+ enableCustomerPortal: true,
182
+ use: [
183
+ checkout({
184
+ products: [
185
+ {
186
+ productId: "your-product-id",
187
+ slug: "pro",
188
+ },
189
+ ],
190
+ successUrl: env.POLAR_SUCCESS_URL,
191
+ authenticatedUsersOnly: true,
192
+ }),
193
+ portal(),
194
+ ],
195
+ }),
196
+ ],
197
+ {{else}}
112
198
  {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
113
199
  plugins: [expo()],
114
200
  {{/if}}
201
+ {{/if}}
115
202
  });
116
203
  {{/if}}
117
204
  {{/if}}
@@ -122,6 +209,10 @@ import { mongodbAdapter } from "better-auth/adapters/mongodb";
122
209
  {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
123
210
  import { expo } from "@better-auth/expo";
124
211
  {{/if}}
212
+ {{#if (eq payments "polar")}}
213
+ import { polar, checkout, portal } from "@polar-sh/better-auth";
214
+ import { polarClient } from "./payments";
215
+ {{/if}}
125
216
  import { client } from "../db";
126
217
 
127
218
  export const auth = betterAuth<BetterAuthOptions>({
@@ -141,9 +232,35 @@ export const auth = betterAuth<BetterAuthOptions>({
141
232
  secure: true,
142
233
  httpOnly: true,
143
234
  },
144
- }
235
+ },
236
+ {{#if (eq payments "polar")}}
237
+ plugins: [
238
+ polar({
239
+ client: polarClient,
240
+ createCustomerOnSignUp: true,
241
+ enableCustomerPortal: true,
242
+ use: [
243
+ checkout({
244
+ products: [
245
+ {
246
+ productId: "your-product-id",
247
+ slug: "pro",
248
+ },
249
+ ],
250
+ successUrl: process.env.POLAR_SUCCESS_URL,
251
+ authenticatedUsersOnly: true,
252
+ }),
253
+ portal(),
254
+ ],
255
+ }),
256
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
257
+ expo(),
258
+ {{/if}}
259
+ ],
260
+ {{else}}
145
261
  {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
146
- , plugins: [expo()]
262
+ plugins: [expo()],
263
+ {{/if}}
147
264
  {{/if}}
148
265
  });
149
266
  {{/if}}
@@ -153,6 +270,10 @@ import { betterAuth, type BetterAuthOptions } from "better-auth";
153
270
  {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
154
271
  import { expo } from "@better-auth/expo";
155
272
  {{/if}}
273
+ {{#if (eq payments "polar")}}
274
+ import { polar, checkout, portal } from "@polar-sh/better-auth";
275
+ import { polarClient } from "./payments";
276
+ {{/if}}
156
277
 
157
278
  export const auth = betterAuth<BetterAuthOptions>({
158
279
  database: "", // Invalid configuration
@@ -171,9 +292,35 @@ export const auth = betterAuth<BetterAuthOptions>({
171
292
  secure: true,
172
293
  httpOnly: true,
173
294
  },
174
- }
295
+ },
296
+ {{#if (eq payments "polar")}}
297
+ plugins: [
298
+ polar({
299
+ client: polarClient,
300
+ createCustomerOnSignUp: true,
301
+ enableCustomerPortal: true,
302
+ use: [
303
+ checkout({
304
+ products: [
305
+ {
306
+ productId: "your-product-id",
307
+ slug: "pro",
308
+ },
309
+ ],
310
+ successUrl: process.env.POLAR_SUCCESS_URL,
311
+ authenticatedUsersOnly: true,
312
+ }),
313
+ portal(),
314
+ ],
315
+ }),
316
+ {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
317
+ expo(),
318
+ {{/if}}
319
+ ],
320
+ {{else}}
175
321
  {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
176
- , plugins: [expo()]
322
+ plugins: [expo()],
323
+ {{/if}}
177
324
  {{/if}}
178
325
  });
179
326
  {{/if}}
@@ -11,9 +11,28 @@ definePageMeta({
11
11
  const { $orpc } = useNuxtApp()
12
12
 
13
13
  const session = $authClient.useSession()
14
+ {{#if (eq payments "polar")}}
15
+ const customerState = ref<any>(null)
16
+ {{/if}}
14
17
 
15
18
  {{#if (eq api "orpc")}}
16
- const privateData = useQuery($orpc.privateData.queryOptions())
19
+ const privateData = useQuery({
20
+ ...$orpc.privateData.queryOptions(),
21
+ enabled: computed(() => !!session.value?.data?.user)
22
+ })
23
+ {{/if}}
24
+
25
+ {{#if (eq payments "polar")}}
26
+ onMounted(async () => {
27
+ if (session.value?.data) {
28
+ const { data } = await $authClient.customer.state()
29
+ customerState.value = data
30
+ }
31
+ })
32
+
33
+ const hasProSubscription = computed(() =>
34
+ customerState.value?.activeSubscriptions?.length! > 0
35
+ )
17
36
  {{/if}}
18
37
 
19
38
  </script>
@@ -27,7 +46,22 @@ const privateData = useQuery($orpc.privateData.queryOptions())
27
46
  {{#if (eq api "orpc")}}
28
47
  <div v-if="privateData.status.value === 'pending'">Loading private data...</div>
29
48
  <div v-else-if="privateData.status.value === 'error'">Error loading private data: \{{ privateData.error.value?.message }}</div>
30
- <p v-else-if="privateData.data.value">Private Data: \{{ privateData.data.value.message }}</p>
49
+ <p v-else-if="privateData.data.value">API: \{{ privateData.data.value.message }}</p>
50
+ {{/if}}
51
+ {{#if (eq payments "polar")}}
52
+ <p class="mb-2">Plan: \{{ hasProSubscription ? "Pro" : "Free" }}</p>
53
+ <UButton
54
+ v-if="hasProSubscription"
55
+ @click="() => { $authClient.customer.portal() }"
56
+ >
57
+ Manage Subscription
58
+ </UButton>
59
+ <UButton
60
+ v-else
61
+ @click="() => { $authClient.checkout({ slug: 'pro' }) }"
62
+ >
63
+ Upgrade to Pro
64
+ </UButton>
31
65
  {{/if}}
32
66
  </div>
33
67
  </template>
@@ -0,0 +1,22 @@
1
+ import { createAuthClient } from "better-auth/vue";
2
+ {{#if (eq payments "polar")}}
3
+ import { polarClient } from "@polar-sh/better-auth";
4
+ {{/if}}
5
+
6
+ export default defineNuxtPlugin((nuxtApp) => {
7
+ const config = useRuntimeConfig();
8
+ const serverUrl = config.public.serverURL;
9
+
10
+ const authClient = createAuthClient({
11
+ baseURL: serverUrl,
12
+ {{#if (eq payments "polar")}}
13
+ plugins: [polarClient()],
14
+ {{/if}}
15
+ });
16
+
17
+ return {
18
+ provide: {
19
+ authClient: authClient,
20
+ },
21
+ };
22
+ });
@@ -1,4 +1,7 @@
1
1
  import { createAuthClient } from "better-auth/react";
2
+ {{#if (eq payments "polar")}}
3
+ import { polarClient } from "@polar-sh/better-auth";
4
+ {{/if}}
2
5
 
3
6
  export const authClient = createAuthClient({
4
7
  baseURL:
@@ -7,4 +10,7 @@ export const authClient = createAuthClient({
7
10
  {{else}}
8
11
  import.meta.env.VITE_SERVER_URL,
9
12
  {{/if}}
13
+ {{#if (eq payments "polar")}}
14
+ plugins: [polarClient()]
15
+ {{/if}}
10
16
  });
@@ -0,0 +1,58 @@
1
+ "use client";
2
+ import { Button } from "@/components/ui/button";
3
+ import { authClient } from "@/lib/auth-client";
4
+ {{#if (eq api "orpc")}}
5
+ import { useQuery } from "@tanstack/react-query";
6
+ import { orpc } from "@/utils/orpc";
7
+ {{/if}}
8
+ {{#if (eq api "trpc")}}
9
+ import { useQuery } from "@tanstack/react-query";
10
+ import { trpc } from "@/utils/trpc";
11
+ {{/if}}
12
+
13
+ export default function Dashboard({
14
+ {{#if (eq payments "polar")}}
15
+ customerState,
16
+ {{/if}}
17
+ session
18
+ }: {
19
+ {{#if (eq payments "polar")}}
20
+ customerState: ReturnType<typeof authClient.customer.state>;
21
+ {{/if}}
22
+ session: typeof authClient.$Infer.Session;
23
+ }) {
24
+ {{#if (eq api "orpc")}}
25
+ const privateData = useQuery(orpc.privateData.queryOptions());
26
+ {{/if}}
27
+ {{#if (eq api "trpc")}}
28
+ const privateData = useQuery(trpc.privateData.queryOptions());
29
+ {{/if}}
30
+
31
+ {{#if (eq payments "polar")}}
32
+ const hasProSubscription = customerState?.activeSubscriptions?.length! > 0;
33
+ console.log("Active subscriptions:", customerState?.activeSubscriptions);
34
+ {{/if}}
35
+
36
+ return (
37
+ <>
38
+ {{#if (eq api "orpc")}}
39
+ <p>API: {privateData.data?.message}</p>
40
+ {{/if}}
41
+ {{#if (eq api "trpc")}}
42
+ <p>API: {privateData.data?.message}</p>
43
+ {{/if}}
44
+ {{#if (eq payments "polar")}}
45
+ <p>Plan: {hasProSubscription ? "Pro" : "Free"}</p>
46
+ {hasProSubscription ? (
47
+ <Button onClick={async () => await authClient.customer.portal()}>
48
+ Manage Subscription
49
+ </Button>
50
+ ) : (
51
+ <Button onClick={async () => await authClient.checkout({ slug: "pro" })}>
52
+ Upgrade to Pro
53
+ </Button>
54
+ )}
55
+ {{/if}}
56
+ </>
57
+ );
58
+ }