create-better-t-stack 2.46.0-canary.876020b1 → 2.46.0-canary.94b70bca

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/cli.js CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { createBtsCli } from "./src-qLVltxMm.js";
2
+ import { createBtsCli } from "./src-DIcX7b9o.js";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
package/dist/index.js CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { builder, createBtsCli, docs, init, router, sponsors } from "./src-qLVltxMm.js";
2
+ import { builder, createBtsCli, docs, init, router, sponsors } from "./src-DIcX7b9o.js";
3
3
 
4
4
  export { builder, createBtsCli, docs, init, router, sponsors };
@@ -121,12 +121,12 @@ const dependencyVersionMap = {
121
121
  "@trpc/tanstack-react-query": "^11.5.0",
122
122
  "@trpc/server": "^11.5.0",
123
123
  "@trpc/client": "^11.5.0",
124
- convex: "^1.25.4",
124
+ convex: "^1.27.0",
125
125
  "@convex-dev/react-query": "^0.0.0-alpha.8",
126
126
  "convex-svelte": "^0.0.11",
127
127
  "convex-nuxt": "0.1.5",
128
128
  "convex-vue": "^0.1.5",
129
- "@convex-dev/better-auth": "^0.8.3",
129
+ "@convex-dev/better-auth": "^0.8.4",
130
130
  "@tanstack/svelte-query": "^5.85.3",
131
131
  "@tanstack/svelte-query-devtools": "^5.85.3",
132
132
  "@tanstack/vue-query-devtools": "^5.83.0",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.46.0-canary.876020b1",
3
+ "version": "2.46.0-canary.94b70bca",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -0,0 +1,40 @@
1
+ "use client"
2
+
3
+ import SignInForm from "@/components/sign-in-form";
4
+ import SignUpForm from "@/components/sign-up-form";
5
+ import UserMenu from "@/components/user-menu";
6
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
7
+ import {
8
+ Authenticated,
9
+ AuthLoading,
10
+ Unauthenticated,
11
+ useQuery,
12
+ } from "convex/react";
13
+ import { useState } from "react";
14
+
15
+ export default function DashboardPage() {
16
+ const [showSignIn, setShowSignIn] = useState(false);
17
+ const privateData = useQuery(api.privateData.get);
18
+
19
+ return (
20
+ <>
21
+ <Authenticated>
22
+ <div>
23
+ <h1>Dashboard</h1>
24
+ <p>privateData: {privateData?.message}</p>
25
+ <UserMenu />
26
+ </div>
27
+ </Authenticated>
28
+ <Unauthenticated>
29
+ {showSignIn ? (
30
+ <SignInForm onSwitchToSignUp={() => setShowSignIn(false)} />
31
+ ) : (
32
+ <SignUpForm onSwitchToSignIn={() => setShowSignIn(true)} />
33
+ )}
34
+ </Unauthenticated>
35
+ <AuthLoading>
36
+ <div>Loading...</div>
37
+ </AuthLoading>
38
+ </>
39
+ );
40
+ }
@@ -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
+ }