create-better-t-stack 2.46.1 → 2.46.3-canary.46360b92

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 (38) hide show
  1. package/dist/cli.js +1 -1
  2. package/dist/index.js +1 -1
  3. package/dist/{src-NOw0j6Z9.js → src-DHpq-szu.js} +132 -35
  4. package/package.json +3 -2
  5. package/templates/auth/better-auth/convex/backend/convex/auth.config.ts.hbs +8 -0
  6. package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +48 -0
  7. package/templates/auth/better-auth/convex/backend/convex/convex.config.ts.hbs +7 -0
  8. package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +12 -0
  9. package/templates/auth/better-auth/convex/backend/convex/privateData.ts.hbs +16 -0
  10. package/templates/auth/better-auth/convex/web/react/next/src/app/api/auth/[...all]/route.ts.hbs +3 -0
  11. package/templates/auth/better-auth/convex/web/react/next/src/app/dashboard/page.tsx.hbs +40 -0
  12. package/templates/auth/better-auth/convex/web/react/next/src/components/sign-in-form.tsx.hbs +129 -0
  13. package/templates/auth/better-auth/convex/web/react/next/src/components/sign-up-form.tsx.hbs +154 -0
  14. package/templates/auth/better-auth/convex/web/react/next/src/components/user-menu.tsx.hbs +48 -0
  15. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-client.ts.hbs +6 -0
  16. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-server.ts.hbs +6 -0
  17. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/sign-in-form.tsx.hbs +133 -0
  18. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/sign-up-form.tsx.hbs +158 -0
  19. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/user-menu.tsx.hbs +50 -0
  20. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/lib/auth-client.ts.hbs +10 -0
  21. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/routes/dashboard.tsx.hbs +43 -0
  22. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/sign-in-form.tsx.hbs +133 -0
  23. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/sign-up-form.tsx.hbs +158 -0
  24. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/user-menu.tsx.hbs +50 -0
  25. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-client.ts.hbs +6 -0
  26. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-server.ts.hbs +5 -0
  27. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/api/auth/$.ts.hbs +11 -0
  28. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/dashboard.tsx.hbs +43 -0
  29. package/templates/db/prisma/postgres/src/db/index.ts.hbs +1 -1
  30. package/templates/examples/ai/web/react/next/src/app/ai/page.tsx.hbs +21 -2
  31. package/templates/frontend/react/next/next.config.ts.hbs +4 -1
  32. package/templates/frontend/react/next/src/components/providers.tsx.hbs +8 -0
  33. package/templates/frontend/react/tanstack-router/src/main.tsx.hbs +8 -1
  34. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +47 -0
  35. package/templates/frontend/react/tanstack-start/src/routes/index.tsx.hbs +2 -2
  36. package/templates/frontend/react/web-base/src/components/header.tsx.hbs +2 -2
  37. package/templates/auth/better-auth/web/react/next/src/components/theme-provider.tsx.hbs +0 -11
  38. /package/templates/frontend/react/web-base/src/components/{loader.tsx → loader.tsx.hbs} +0 -0
@@ -0,0 +1,154 @@
1
+ import { authClient } from "@/lib/auth-client";
2
+ import { useForm } from "@tanstack/react-form";
3
+ import { toast } from "sonner";
4
+ import z from "zod";
5
+ import { Button } from "./ui/button";
6
+ import { Input } from "./ui/input";
7
+ import { Label } from "./ui/label";
8
+ import { useRouter } from "next/navigation";
9
+
10
+ export default function SignUpForm({
11
+ onSwitchToSignIn,
12
+ }: {
13
+ onSwitchToSignIn: () => void;
14
+ }) {
15
+ const router = useRouter();
16
+
17
+ const form = useForm({
18
+ defaultValues: {
19
+ email: "",
20
+ password: "",
21
+ name: "",
22
+ },
23
+ onSubmit: async ({ value }) => {
24
+ await authClient.signUp.email(
25
+ {
26
+ email: value.email,
27
+ password: value.password,
28
+ name: value.name,
29
+ },
30
+ {
31
+ onSuccess: () => {
32
+ router.push("/dashboard");
33
+ toast.success("Sign up successful");
34
+ },
35
+ onError: (error) => {
36
+ toast.error(error.error.message || error.error.statusText);
37
+ },
38
+ },
39
+ );
40
+ },
41
+ validators: {
42
+ onSubmit: z.object({
43
+ name: z.string().min(2, "Name must be at least 2 characters"),
44
+ email: z.email("Invalid email address"),
45
+ password: z.string().min(8, "Password must be at least 8 characters"),
46
+ }),
47
+ },
48
+ });
49
+
50
+ return (
51
+ <div className="mx-auto w-full mt-10 max-w-md p-6">
52
+ <h1 className="mb-6 text-center text-3xl font-bold">Create Account</h1>
53
+
54
+ <form
55
+ onSubmit={(e) => {
56
+ e.preventDefault();
57
+ e.stopPropagation();
58
+ form.handleSubmit();
59
+ }}
60
+ className="space-y-4"
61
+ >
62
+ <div>
63
+ <form.Field name="name">
64
+ {(field) => (
65
+ <div className="space-y-2">
66
+ <Label htmlFor={field.name}>Name</Label>
67
+ <Input
68
+ id={field.name}
69
+ name={field.name}
70
+ value={field.state.value}
71
+ onBlur={field.handleBlur}
72
+ onChange={(e) => field.handleChange(e.target.value)}
73
+ />
74
+ {field.state.meta.errors.map((error) => (
75
+ <p key={error?.message} className="text-red-500">
76
+ {error?.message}
77
+ </p>
78
+ ))}
79
+ </div>
80
+ )}
81
+ </form.Field>
82
+ </div>
83
+
84
+ <div>
85
+ <form.Field name="email">
86
+ {(field) => (
87
+ <div className="space-y-2">
88
+ <Label htmlFor={field.name}>Email</Label>
89
+ <Input
90
+ id={field.name}
91
+ name={field.name}
92
+ type="email"
93
+ value={field.state.value}
94
+ onBlur={field.handleBlur}
95
+ onChange={(e) => field.handleChange(e.target.value)}
96
+ />
97
+ {field.state.meta.errors.map((error) => (
98
+ <p key={error?.message} className="text-red-500">
99
+ {error?.message}
100
+ </p>
101
+ ))}
102
+ </div>
103
+ )}
104
+ </form.Field>
105
+ </div>
106
+
107
+ <div>
108
+ <form.Field name="password">
109
+ {(field) => (
110
+ <div className="space-y-2">
111
+ <Label htmlFor={field.name}>Password</Label>
112
+ <Input
113
+ id={field.name}
114
+ name={field.name}
115
+ type="password"
116
+ value={field.state.value}
117
+ onBlur={field.handleBlur}
118
+ onChange={(e) => field.handleChange(e.target.value)}
119
+ />
120
+ {field.state.meta.errors.map((error) => (
121
+ <p key={error?.message} className="text-red-500">
122
+ {error?.message}
123
+ </p>
124
+ ))}
125
+ </div>
126
+ )}
127
+ </form.Field>
128
+ </div>
129
+
130
+ <form.Subscribe>
131
+ {(state) => (
132
+ <Button
133
+ type="submit"
134
+ className="w-full"
135
+ disabled={!state.canSubmit || state.isSubmitting}
136
+ >
137
+ {state.isSubmitting ? "Submitting..." : "Sign Up"}
138
+ </Button>
139
+ )}
140
+ </form.Subscribe>
141
+ </form>
142
+
143
+ <div className="mt-4 text-center">
144
+ <Button
145
+ variant="link"
146
+ onClick={onSwitchToSignIn}
147
+ className="text-indigo-600 hover:text-indigo-800"
148
+ >
149
+ Already have an account? Sign In
150
+ </Button>
151
+ </div>
152
+ </div>
153
+ );
154
+ }
@@ -0,0 +1,48 @@
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 { Button } from "./ui/button";
11
+ import { useRouter } from "next/navigation";
12
+ import { useQuery } from "convex/react";
13
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
14
+
15
+ export default function UserMenu() {
16
+ const router = useRouter();
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
+ router.push("/dashboard");
37
+ },
38
+ },
39
+ });
40
+ }}
41
+ >
42
+ Sign Out
43
+ </Button>
44
+ </DropdownMenuItem>
45
+ </DropdownMenuContent>
46
+ </DropdownMenu>
47
+ );
48
+ }
@@ -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,6 @@
1
+ import { createAuth } from "@{{projectName}}/backend/convex/auth";
2
+ import { getToken as getTokenNextjs } from "@convex-dev/better-auth/nextjs";
3
+
4
+ export const getToken = () => {
5
+ return getTokenNextjs(createAuth);
6
+ };
@@ -0,0 +1,133 @@
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 SignInForm({
11
+ onSwitchToSignUp,
12
+ }: {
13
+ onSwitchToSignUp: () => void;
14
+ }) {
15
+ const navigate = useNavigate({
16
+ from: "/",
17
+ });
18
+
19
+ const form = useForm({
20
+ defaultValues: {
21
+ email: "",
22
+ password: "",
23
+ },
24
+ onSubmit: async ({ value }) => {
25
+ await authClient.signIn.email(
26
+ {
27
+ email: value.email,
28
+ password: value.password,
29
+ },
30
+ {
31
+ onSuccess: () => {
32
+ navigate({
33
+ to: "/dashboard",
34
+ });
35
+ toast.success("Sign in successful");
36
+ },
37
+ onError: (error) => {
38
+ toast.error(error.error.message || error.error.statusText);
39
+ },
40
+ },
41
+ );
42
+ },
43
+ validators: {
44
+ onSubmit: z.object({
45
+ email: z.email("Invalid email address"),
46
+ password: z.string().min(8, "Password must be at least 8 characters"),
47
+ }),
48
+ },
49
+ });
50
+
51
+ return (
52
+ <div className="mx-auto w-full mt-10 max-w-md p-6">
53
+ <h1 className="mb-6 text-center text-3xl font-bold">Welcome Back</h1>
54
+
55
+ <form
56
+ onSubmit={(e) => {
57
+ e.preventDefault();
58
+ e.stopPropagation();
59
+ form.handleSubmit();
60
+ }}
61
+ className="space-y-4"
62
+ >
63
+ <div>
64
+ <form.Field name="email">
65
+ {(field) => (
66
+ <div className="space-y-2">
67
+ <Label htmlFor={field.name}>Email</Label>
68
+ <Input
69
+ id={field.name}
70
+ name={field.name}
71
+ type="email"
72
+ value={field.state.value}
73
+ onBlur={field.handleBlur}
74
+ onChange={(e) => field.handleChange(e.target.value)}
75
+ />
76
+ {field.state.meta.errors.map((error) => (
77
+ <p key={error?.message} className="text-red-500">
78
+ {error?.message}
79
+ </p>
80
+ ))}
81
+ </div>
82
+ )}
83
+ </form.Field>
84
+ </div>
85
+
86
+ <div>
87
+ <form.Field name="password">
88
+ {(field) => (
89
+ <div className="space-y-2">
90
+ <Label htmlFor={field.name}>Password</Label>
91
+ <Input
92
+ id={field.name}
93
+ name={field.name}
94
+ type="password"
95
+ value={field.state.value}
96
+ onBlur={field.handleBlur}
97
+ onChange={(e) => field.handleChange(e.target.value)}
98
+ />
99
+ {field.state.meta.errors.map((error) => (
100
+ <p key={error?.message} className="text-red-500">
101
+ {error?.message}
102
+ </p>
103
+ ))}
104
+ </div>
105
+ )}
106
+ </form.Field>
107
+ </div>
108
+
109
+ <form.Subscribe>
110
+ {(state) => (
111
+ <Button
112
+ type="submit"
113
+ className="w-full"
114
+ disabled={!state.canSubmit || state.isSubmitting}
115
+ >
116
+ {state.isSubmitting ? "Submitting..." : "Sign In"}
117
+ </Button>
118
+ )}
119
+ </form.Subscribe>
120
+ </form>
121
+
122
+ <div className="mt-4 text-center">
123
+ <Button
124
+ variant="link"
125
+ onClick={onSwitchToSignUp}
126
+ className="text-indigo-600 hover:text-indigo-800"
127
+ >
128
+ Need an account? Sign Up
129
+ </Button>
130
+ </div>
131
+ </div>
132
+ );
133
+ }
@@ -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,10 @@
1
+ import { createAuthClient } from "better-auth/react";
2
+ import {
3
+ convexClient,
4
+ crossDomainClient,
5
+ } from "@convex-dev/better-auth/client/plugins";
6
+
7
+ export const authClient = createAuthClient({
8
+ baseURL: import.meta.env.VITE_CONVEX_SITE_URL,
9
+ plugins: [convexClient(), crossDomainClient()],
10
+ });
@@ -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
+ }