create-better-t-stack 2.45.4 → 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 +163 -16
  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,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
+ }
@@ -1,47 +1,37 @@
1
- "use client"
2
1
  import { authClient } from "@/lib/auth-client";
3
- {{#if (eq api "orpc")}}
4
- import { useQuery } from "@tanstack/react-query";
5
- import { orpc } from "@/utils/orpc";
6
- {{/if}}
7
- {{#if (eq api "trpc")}}
8
- import { useQuery } from "@tanstack/react-query";
9
- import { trpc } from "@/utils/trpc";
10
- {{/if}}
11
- import { useRouter } from "next/navigation";
12
- import { useEffect } from "react";
2
+ import { redirect } from "next/navigation";
3
+ import Dashboard from "./dashboard";
4
+ import { headers } from "next/headers";
13
5
 
14
- export default function Dashboard() {
15
- const router = useRouter();
16
- const { data: session, isPending } = authClient.useSession();
6
+ export default async function DashboardPage() {
7
+ const session = await authClient.getSession({
8
+ fetchOptions: {
9
+ headers: await headers()
10
+ }
11
+ });
17
12
 
18
- {{#if (eq api "orpc")}}
19
- const privateData = useQuery(orpc.privateData.queryOptions());
20
- {{/if}}
21
- {{#if (eq api "trpc")}}
22
- const privateData = useQuery(trpc.privateData.queryOptions());
23
- {{/if}}
13
+ if (!session.data) {
14
+ redirect("/login");
15
+ }
24
16
 
25
- useEffect(() => {
26
- if (!session && !isPending) {
27
- router.push("/login");
28
- }
29
- }, [session, isPending]);
17
+ {{#if (eq payments "polar")}}
18
+ const { data: customerState, error } = await authClient.customer.state({
19
+ fetchOptions: {
20
+ headers: await headers()
21
+ }
22
+ });
23
+ {{/if}}
30
24
 
31
- if (isPending) {
32
- return <div>Loading...</div>;
33
- }
34
-
35
- return (
36
- <div>
37
- <h1>Dashboard</h1>
38
- <p>Welcome {session?.user.name}</p>
39
- {{#if (eq api "orpc")}}
40
- <p>privateData: {privateData.data?.message}</p>
41
- {{/if}}
42
- {{#if (eq api "trpc")}}
43
- <p>privateData: {privateData.data?.message}</p>
44
- {{/if}}
45
- </div>
46
- );
25
+ return (
26
+ <div>
27
+ <h1>Dashboard</h1>
28
+ <p>Welcome {session.data.user.name}</p>
29
+ <Dashboard
30
+ session={session.data}
31
+ {{#if (eq payments "polar")}}
32
+ customerState={customerState}
33
+ {{/if}}
34
+ />
35
+ </div>
36
+ );
47
37
  }
@@ -1,3 +1,4 @@
1
+ import { Button } from "@/components/ui/button";
1
2
  import { authClient } from "@/lib/auth-client";
2
3
  {{#if (eq api "orpc")}}
3
4
  import { orpc } from "@/utils/orpc";
@@ -8,12 +9,15 @@ import { trpc } from "@/utils/trpc";
8
9
  {{#if ( or (eq api "orpc") (eq api "trpc"))}}
9
10
  import { useQuery } from "@tanstack/react-query";
10
11
  {{/if}}
11
- import { useEffect } from "react";
12
+ import { useEffect, useState } from "react";
12
13
  import { useNavigate } from "react-router";
13
14
 
14
15
  export default function Dashboard() {
15
16
  const { data: session, isPending } = authClient.useSession();
16
17
  const navigate = useNavigate();
18
+ {{#if (eq payments "polar")}}
19
+ const [customerState, setCustomerState] = useState<any>(null);
20
+ {{/if}}
17
21
 
18
22
  {{#if (eq api "orpc")}}
19
23
  const privateData = useQuery(orpc.privateData.queryOptions());
@@ -26,18 +30,48 @@ export default function Dashboard() {
26
30
  if (!session && !isPending) {
27
31
  navigate("/login");
28
32
  }
29
- }, [session, isPending]);
33
+ }, [session, isPending, navigate]);
34
+
35
+ {{#if (eq payments "polar")}}
36
+ useEffect(() => {
37
+ async function fetchCustomerState() {
38
+ if (session) {
39
+ const { data } = await authClient.customer.state();
40
+ setCustomerState(data);
41
+ }
42
+ }
43
+
44
+ fetchCustomerState();
45
+ }, [session]);
46
+ {{/if}}
30
47
 
31
48
  if (isPending) {
32
49
  return <div>Loading...</div>;
33
50
  }
34
51
 
52
+ {{#if (eq payments "polar")}}
53
+ const hasProSubscription = customerState?.activeSubscriptions?.length! > 0;
54
+ console.log("Active subscriptions:", customerState?.activeSubscriptions);
55
+ {{/if}}
56
+
35
57
  return (
36
58
  <div>
37
59
  <h1>Dashboard</h1>
38
60
  <p>Welcome {session?.user.name}</p>
39
61
  {{#if ( or (eq api "orpc") (eq api "trpc"))}}
40
- <p>privateData: {privateData.data?.message}</p>
62
+ <p>API: {privateData.data?.message}</p>
63
+ {{/if}}
64
+ {{#if (eq payments "polar")}}
65
+ <p>Plan: {hasProSubscription ? "Pro" : "Free"}</p>
66
+ {hasProSubscription ? (
67
+ <Button onClick={async () => await authClient.customer.portal()}>
68
+ Manage Subscription
69
+ </Button>
70
+ ) : (
71
+ <Button onClick={async () => await authClient.checkout({ slug: "pro" })}>
72
+ Upgrade to Pro
73
+ </Button>
74
+ )}
41
75
  {{/if}}
42
76
  </div>
43
77
  );
@@ -1,3 +1,4 @@
1
+ import { Button } from "@/components/ui/button";
1
2
  import { authClient } from "@/lib/auth-client";
2
3
  {{#if (eq api "orpc")}}
3
4
  import { orpc } from "@/utils/orpc";
@@ -8,44 +9,61 @@ import { trpc } from "@/utils/trpc";
8
9
  {{#if ( or (eq api "orpc") (eq api "trpc"))}}
9
10
  import { useQuery } from "@tanstack/react-query";
10
11
  {{/if}}
11
- import { createFileRoute } from "@tanstack/react-router";
12
- import { useEffect } from "react";
12
+ import { createFileRoute, redirect } from "@tanstack/react-router";
13
13
 
14
14
  export const Route = createFileRoute("/dashboard")({
15
- component: RouteComponent,
15
+ component: RouteComponent,
16
+ beforeLoad: async () => {
17
+ const session = await authClient.getSession();
18
+ if (!session.data) {
19
+ redirect({
20
+ to: "/login",
21
+ throw: true
22
+ });
23
+ }
24
+ {{#if (eq payments "polar")}}
25
+ const {data: customerState} = await authClient.customer.state()
26
+ return { session, customerState };
27
+ {{else}}
28
+ return { session };
29
+ {{/if}}
30
+ }
16
31
  });
17
32
 
18
33
  function RouteComponent() {
19
- const { data: session, isPending } = authClient.useSession();
34
+ const { session{{#if (eq payments "polar")}}, customerState{{/if}} } = Route.useRouteContext();
20
35
 
21
- const navigate = Route.useNavigate();
36
+ {{#if (eq api "orpc")}}
37
+ const privateData = useQuery(orpc.privateData.queryOptions());
38
+ {{/if}}
39
+ {{#if (eq api "trpc")}}
40
+ const privateData = useQuery(trpc.privateData.queryOptions());
41
+ {{/if}}
22
42
 
23
- {{#if (eq api "orpc")}}
24
- const privateData = useQuery(orpc.privateData.queryOptions());
25
- {{/if}}
26
- {{#if (eq api "trpc")}}
27
- const privateData = useQuery(trpc.privateData.queryOptions());
28
- {{/if}}
43
+ {{#if (eq payments "polar")}}
44
+ const hasProSubscription = customerState?.activeSubscriptions?.length! > 0
45
+ console.log("Active subscriptions:", customerState?.activeSubscriptions)
46
+ {{/if}}
29
47
 
30
- useEffect(() => {
31
- if (!session && !isPending) {
32
- navigate({
33
- to: "/login",
34
- });
35
- }
36
- }, [session, isPending]);
37
-
38
- if (isPending) {
39
- return <div>Loading...</div>;
40
- }
41
-
42
- return (
43
- <div>
44
- <h1>Dashboard</h1>
45
- <p>Welcome {session?.user.name}</p>
46
- {{#if ( or (eq api "orpc") (eq api "trpc"))}}
47
- <p>privateData: {privateData.data?.message}</p>
48
- {{/if}}
49
- </div>
50
- );
48
+ return (
49
+ <div>
50
+ <h1>Dashboard</h1>
51
+ <p>Welcome {session.data?.user.name}</p>
52
+ {{#if ( or (eq api "orpc") (eq api "trpc"))}}
53
+ <p>API: {privateData.data?.message}</p>
54
+ {{/if}}
55
+ {{#if (eq payments "polar")}}
56
+ <p>Plan: {hasProSubscription ? "Pro" : "Free"}</p>
57
+ {hasProSubscription ? (
58
+ <Button onClick={async () => await authClient.customer.portal()}>
59
+ Manage Subscription
60
+ </Button>
61
+ ) : (
62
+ <Button onClick={async () => await authClient.checkout({ slug: "pro" })}>
63
+ Upgrade to Pro
64
+ </Button>
65
+ )}
66
+ {{/if}}
67
+ </div>
68
+ );
51
69
  }
@@ -1,3 +1,4 @@
1
+ import { Button } from "@/components/ui/button";
1
2
  import { authClient } from "@/lib/auth-client";
2
3
  {{#if (eq api "trpc")}}
3
4
  import { useTRPC } from "@/utils/trpc";
@@ -7,48 +8,62 @@ import { useQuery } from "@tanstack/react-query";
7
8
  import { orpc } from "@/utils/orpc";
8
9
  import { useQuery } from "@tanstack/react-query";
9
10
  {{/if}}
10
- import { createFileRoute } from "@tanstack/react-router";
11
- import { useEffect } from "react";
11
+ import { createFileRoute, redirect } from "@tanstack/react-router";
12
12
 
13
13
  export const Route = createFileRoute("/dashboard")({
14
- component: RouteComponent,
14
+ component: RouteComponent,
15
+ beforeLoad: async () => {
16
+ const session = await authClient.getSession();
17
+ if (!session.data) {
18
+ redirect({
19
+ to: "/login",
20
+ throw: true
21
+ });
22
+ }
23
+ {{#if (eq payments "polar")}}
24
+ const {data: customerState} = await authClient.customer.state()
25
+ return { session, customerState };
26
+ {{else}}
27
+ return { session };
28
+ {{/if}}
29
+ }
15
30
  });
16
31
 
17
32
  function RouteComponent() {
18
- const navigate = Route.useNavigate();
19
- {{#if (eq api "trpc")}}
20
- const trpc = useTRPC();
21
- {{/if}}
22
- {{#if (eq api "orpc")}}
23
- {{/if}}
24
- const { data: session, isPending } = authClient.useSession();
33
+ const { session{{#if (eq payments "polar")}}, customerState{{/if}} } = Route.useRouteContext();
25
34
 
26
- {{#if (eq api "trpc")}}
27
- const privateData = useQuery(trpc.privateData.queryOptions());
28
- {{/if}}
29
- {{#if (eq api "orpc")}}
30
- const privateData = useQuery(orpc.privateData.queryOptions());
31
- {{/if}}
35
+ {{#if (eq api "trpc")}}
36
+ const trpc = useTRPC();
37
+ const privateData = useQuery(trpc.privateData.queryOptions());
38
+ {{/if}}
39
+ {{#if (eq api "orpc")}}
40
+ const privateData = useQuery(orpc.privateData.queryOptions());
41
+ {{/if}}
32
42
 
33
- useEffect(() => {
34
- if (!session && !isPending) {
35
- navigate({
36
- to: "/login",
37
- });
38
- }
39
- }, [session, isPending]);
43
+ {{#if (eq payments "polar")}}
44
+ const hasProSubscription = customerState?.activeSubscriptions?.length! > 0
45
+ console.log("Active subscriptions:", customerState?.activeSubscriptions)
46
+ {{/if}}
40
47
 
41
- if (isPending) {
42
- return <div>Loading...</div>;
43
- }
44
-
45
- return (
46
- <div>
47
- <h1>Dashboard</h1>
48
- <p>Welcome {session?.user.name}</p>
49
- {{#if ( or (eq api "orpc") (eq api "trpc"))}}
50
- <p>privateData: {privateData.data?.message}</p>
51
- {{/if}}
52
- </div>
53
- );
48
+ return (
49
+ <div>
50
+ <h1>Dashboard</h1>
51
+ <p>Welcome {session.data?.user.name}</p>
52
+ {{#if ( or (eq api "orpc") (eq api "trpc"))}}
53
+ <p>API: {privateData.data?.message}</p>
54
+ {{/if}}
55
+ {{#if (eq payments "polar")}}
56
+ <p>Plan: {hasProSubscription ? "Pro" : "Free"}</p>
57
+ {hasProSubscription ? (
58
+ <Button onClick={async () => await authClient.customer.portal()}>
59
+ Manage Subscription
60
+ </Button>
61
+ ) : (
62
+ <Button onClick={async () => await authClient.checkout({ slug: "pro" })}>
63
+ Upgrade to Pro
64
+ </Button>
65
+ )}
66
+ {{/if}}
67
+ </div>
68
+ );
54
69
  }
@@ -0,0 +1,11 @@
1
+ import { createAuthClient } from "better-auth/solid";
2
+ {{#if (eq payments "polar")}}
3
+ import { polarClient } from "@polar-sh/better-auth";
4
+ {{/if}}
5
+
6
+ export const authClient = createAuthClient({
7
+ baseURL: import.meta.env.VITE_SERVER_URL,
8
+ {{#if (eq payments "polar")}}
9
+ plugins: [polarClient()]
10
+ {{/if}}
11
+ });
@@ -3,42 +3,65 @@ import { authClient } from "@/lib/auth-client";
3
3
  import { orpc } from "@/utils/orpc";
4
4
  import { useQuery } from "@tanstack/solid-query";
5
5
  {{/if}}
6
- import { createFileRoute } from "@tanstack/solid-router";
7
- import { createEffect, Show } from "solid-js";
6
+ import { createFileRoute, redirect } from "@tanstack/solid-router";
8
7
 
9
8
  export const Route = createFileRoute("/dashboard")({
10
- component: RouteComponent,
9
+ component: RouteComponent,
10
+ beforeLoad: async () => {
11
+ const session = await authClient.getSession();
12
+ if (!session.data) {
13
+ redirect({
14
+ to: "/login",
15
+ throw: true,
16
+ });
17
+ }
18
+ {{#if (eq payments "polar")}}
19
+ const { data: customerState } = await authClient.customer.state();
20
+ return { session, customerState };
21
+ {{else}}
22
+ return { session };
23
+ {{/if}}
24
+ },
11
25
  });
12
26
 
13
27
  function RouteComponent() {
14
- const session = authClient.useSession();
15
- const navigate = Route.useNavigate();
28
+ const context = Route.useRouteContext();
16
29
 
17
- {{#if (eq api "orpc")}}
18
- const privateData = useQuery(() => orpc.privateData.queryOptions());
19
- {{/if}}
30
+ const session = context().session;
31
+ {{#if (eq payments "polar")}}
32
+ const customerState = context().customerState;
33
+ {{/if}}
20
34
 
21
- createEffect(() => {
22
- if (!session().data && !session().isPending) {
23
- navigate({
24
- to: "/login",
25
- });
26
- }
27
- });
35
+ {{#if (eq api "orpc")}}
36
+ const privateData = useQuery(() => orpc.privateData.queryOptions());
37
+ {{/if}}
28
38
 
29
- return (
30
- <div>
31
- <Show when={session().isPending}>
32
- <div>Loading...</div>
33
- </Show>
39
+ {{#if (eq payments "polar")}}
40
+ const hasProSubscription = () =>
41
+ customerState?.activeSubscriptions?.length! > 0;
42
+ {{/if}}
34
43
 
35
- <Show when={!session().isPending && session().data}>
36
- <h1>Dashboard</h1>
37
- <p>Welcome {session().data?.user.name}</p>
38
- {{#if (eq api "orpc")}}
39
- <p>privateData: {privateData.data?.message}</p>
40
- {{/if}}
41
- </Show>
42
- </div>
43
- );
44
+ return (
45
+ <div>
46
+ <h1>Dashboard</h1>
47
+ <p>Welcome {session.data?.user.name}</p>
48
+ {{#if (eq api "orpc")}}
49
+ <p>API: {privateData.data?.message}</p>
50
+ {{/if}}
51
+ {{#if (eq payments "polar")}}
52
+ <p>Plan: {hasProSubscription() ? "Pro" : "Free"}</p>
53
+ {hasProSubscription() ? (
54
+ <button onClick={async () => await authClient.customer.portal()}>
55
+ Manage Subscription
56
+ </button>
57
+ ) : (
58
+ <button
59
+ onClick={async () => await authClient.checkout({ slug: "pro" })}
60
+ >
61
+ Upgrade to Pro
62
+ </button>
63
+ )}
64
+ {{/if}}
65
+ </div>
66
+ );
44
67
  }
@@ -1,6 +1,12 @@
1
1
  import { PUBLIC_SERVER_URL } from "$env/static/public";
2
2
  import { createAuthClient } from "better-auth/svelte";
3
+ {{#if (eq payments "polar")}}
4
+ import { polarClient } from "@polar-sh/better-auth";
5
+ {{/if}}
3
6
 
4
7
  export const authClient = createAuthClient({
5
8
  baseURL: PUBLIC_SERVER_URL,
9
+ {{#if (eq payments "polar")}}
10
+ plugins: [polarClient()]
11
+ {{/if}}
6
12
  });
@@ -6,7 +6,9 @@
6
6
  import { orpc } from '$lib/orpc';
7
7
  import { createQuery } from '@tanstack/svelte-query';
8
8
  {{/if}}
9
- import { get } from 'svelte/store';
9
+ {{#if (eq payments "polar")}}
10
+ let customerState: any = null;
11
+ {{/if}}
10
12
 
11
13
  const sessionQuery = authClient.useSession();
12
14
 
@@ -15,10 +17,17 @@
15
17
  {{/if}}
16
18
 
17
19
  onMount(() => {
18
- const { data: session, isPending } = get(sessionQuery);
20
+ const { data: session, isPending } = $sessionQuery;
19
21
  if (!session && !isPending) {
20
22
  goto('/login');
21
23
  }
24
+ {{#if (eq payments "polar")}}
25
+ if (session) {
26
+ authClient.customer.state().then(({ data }) => {
27
+ customerState = data;
28
+ });
29
+ }
30
+ {{/if}}
22
31
  });
23
32
  </script>
24
33
 
@@ -30,7 +39,19 @@
30
39
  <h1>Dashboard</h1>
31
40
  <p>Welcome {$sessionQuery.data.user.name}</p>
32
41
  {{#if (eq api "orpc")}}
33
- <p>privateData: {$privateDataQuery.data?.message}</p>
42
+ <p>API: {$privateDataQuery.data?.message}</p>
43
+ {{/if}}
44
+ {{#if (eq payments "polar")}}
45
+ <p>Plan: {customerState?.activeSubscriptions?.length > 0 ? "Pro" : "Free"}</p>
46
+ {#if customerState?.activeSubscriptions?.length > 0}
47
+ <button onclick={async () => await authClient.customer.portal()}>
48
+ Manage Subscription
49
+ </button>
50
+ {:else}
51
+ <button onclick={async () => await authClient.checkout({ slug: "pro" })}>
52
+ Upgrade to Pro
53
+ </button>
54
+ {/if}
34
55
  {{/if}}
35
56
  </div>
36
57
  {/if}
@@ -17,7 +17,7 @@
17
17
  ],
18
18
  {{/if}}
19
19
  "devDependencies": {
20
- "tsdown": "^0.14.1",
20
+ "tsdown": "^0.15.1",
21
21
  "typescript": "^5.8.2"
22
22
  }
23
23
  }
@@ -1,2 +1,6 @@
1
1
  [install]
2
+ {{#if (includes frontend "nuxt")}}
3
+ # linker = "isolated" # Commented out for Nuxt compatibility
4
+ {{else}}
2
5
  linker = "isolated"
6
+ {{/if}}
@@ -5,6 +5,10 @@
5
5
  import { useAuth } from "@clerk/nextjs";
6
6
  import { ConvexReactClient } from "convex/react";
7
7
  import { ConvexProviderWithClerk } from "convex/react-clerk";
8
+ {{else if (eq auth "better-auth")}}
9
+ import { ConvexProvider, ConvexReactClient } from "convex/react";
10
+ import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react";
11
+ import { authClient } from "@/lib/auth-client";
8
12
  {{else}}
9
13
  import { ConvexProvider, ConvexReactClient } from "convex/react";
10
14
  {{/if}}
@@ -44,6 +48,10 @@ export default function Providers({
44
48
  <ConvexProviderWithClerk client={convex} useAuth={useAuth}>
45
49
  {children}
46
50
  </ConvexProviderWithClerk>
51
+ {{else if (eq auth "better-auth")}}
52
+ <ConvexBetterAuthProvider client={convex} authClient={authClient}>
53
+ {children}
54
+ </ConvexBetterAuthProvider>
47
55
  {{else}}
48
56
  <ConvexProvider client={convex}>{children}</ConvexProvider>
49
57
  {{/if}}
@@ -16,10 +16,15 @@ import { routeTree } from "./routeTree.gen";
16
16
  {{#if (eq auth "clerk")}}
17
17
  import { ClerkProvider, useAuth } from "@clerk/clerk-react";
18
18
  import { ConvexProviderWithClerk } from "convex/react-clerk";
19
+ {{else if (eq auth "better-auth")}}
20
+ import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react";
21
+ import { authClient } from "@/lib/auth-client";
19
22
  {{else}}
20
23
  import { ConvexProvider } from "convex/react";
21
24
  {{/if}}
22
- const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string);
25
+ const convex = new ConvexReactClient(import.meta.env.VITE_CONVEX_URL as string{{#if (eq auth "better-auth")}}, {
26
+ expectAuth: true,
27
+ }{{/if}});
23
28
  {{/if}}
24
29
 
25
30
  const router = createRouter({
@@ -57,6 +62,8 @@ const router = createRouter({
57
62
  </ConvexProviderWithClerk>
58
63
  </ClerkProvider>
59
64
  );
65
+ {{else if (eq auth "better-auth")}}
66
+ return <ConvexBetterAuthProvider client={convex} authClient={authClient}>{children}</ConvexBetterAuthProvider>;
60
67
  {{else}}
61
68
  return <ConvexProvider client={convex}>{children}</ConvexProvider>;
62
69
  {{/if}}