create-better-t-stack 3.9.0 → 3.11.0

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 (77) hide show
  1. package/README.md +2 -1
  2. package/dist/cli.mjs +1 -1
  3. package/dist/index.d.mts +7 -3
  4. package/dist/index.mjs +1 -1
  5. package/dist/{src-DLvUK0Qf.mjs → src-XVvJUQ_h.mjs} +270 -93
  6. package/package.json +44 -44
  7. package/templates/auth/better-auth/convex/backend/convex/auth.config.ts.hbs +5 -7
  8. package/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +17 -17
  9. package/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +4 -4
  10. package/templates/auth/better-auth/convex/web/react/next/src/app/api/auth/[...all]/route.ts.hbs +2 -2
  11. package/templates/auth/better-auth/convex/web/react/next/src/components/user-menu.tsx.hbs +10 -10
  12. package/templates/auth/better-auth/convex/web/react/next/src/lib/auth-server.ts.hbs +13 -5
  13. package/templates/auth/better-auth/convex/web/react/tanstack-router/src/components/user-menu.tsx.hbs +14 -12
  14. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/components/user-menu.tsx.hbs +13 -16
  15. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/lib/auth-server.ts.hbs +11 -5
  16. package/templates/auth/better-auth/convex/web/react/tanstack-start/src/routes/api/auth/$.ts.hbs +4 -4
  17. package/templates/auth/better-auth/fullstack/tanstack-start/src/routes/api/auth/$.ts.hbs +1 -1
  18. package/templates/auth/better-auth/web/react/next/src/components/user-menu.tsx.hbs +17 -15
  19. package/templates/auth/better-auth/web/react/react-router/src/components/user-menu.tsx.hbs +16 -15
  20. package/templates/auth/better-auth/web/react/tanstack-router/src/components/{user-menu.tsx → user-menu.tsx.hbs} +16 -15
  21. package/templates/auth/better-auth/web/react/tanstack-start/src/components/{user-menu.tsx → user-menu.tsx.hbs} +16 -15
  22. package/templates/backend/convex/packages/backend/convex/README.md +4 -4
  23. package/templates/backend/convex/packages/backend/convex/convex.config.ts.hbs +17 -0
  24. package/templates/backend/convex/packages/backend/convex/tsconfig.json.hbs +1 -1
  25. package/templates/examples/ai/convex/packages/backend/convex/agent.ts.hbs +9 -0
  26. package/templates/examples/ai/convex/packages/backend/convex/chat.ts.hbs +67 -0
  27. package/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs +301 -3
  28. package/templates/examples/ai/native/unistyles/app/(drawer)/ai.tsx.hbs +296 -10
  29. package/templates/examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs +180 -1
  30. package/templates/examples/ai/web/react/next/src/app/ai/page.tsx.hbs +172 -9
  31. package/templates/examples/ai/web/react/react-router/src/routes/ai.tsx.hbs +156 -6
  32. package/templates/examples/ai/web/react/tanstack-router/src/routes/ai.tsx.hbs +156 -4
  33. package/templates/examples/ai/web/react/tanstack-start/src/routes/ai.tsx.hbs +159 -6
  34. package/templates/frontend/react/next/package.json.hbs +8 -7
  35. package/templates/frontend/react/next/src/app/layout.tsx.hbs +28 -1
  36. package/templates/frontend/react/next/src/components/mode-toggle.tsx.hbs +4 -6
  37. package/templates/frontend/react/next/src/components/providers.tsx.hbs +14 -4
  38. package/templates/frontend/react/react-router/package.json.hbs +2 -1
  39. package/templates/frontend/react/{tanstack-router/src/components/mode-toggle.tsx → react-router/src/components/mode-toggle.tsx.hbs} +4 -6
  40. package/templates/frontend/react/tanstack-router/package.json.hbs +2 -1
  41. package/templates/frontend/react/{react-router/src/components/mode-toggle.tsx → tanstack-router/src/components/mode-toggle.tsx.hbs} +4 -6
  42. package/templates/frontend/react/tanstack-start/package.json.hbs +2 -1
  43. package/templates/frontend/react/tanstack-start/src/router.tsx.hbs +6 -0
  44. package/templates/frontend/react/tanstack-start/src/routes/__root.tsx.hbs +13 -14
  45. package/templates/frontend/react/tanstack-start/vite.config.ts.hbs +5 -0
  46. package/templates/frontend/react/web-base/components.json +5 -2
  47. package/templates/frontend/react/web-base/src/components/ui/button.tsx.hbs +57 -0
  48. package/templates/frontend/react/web-base/src/components/ui/card.tsx.hbs +103 -0
  49. package/templates/frontend/react/web-base/src/components/ui/checkbox.tsx.hbs +26 -0
  50. package/templates/frontend/react/web-base/src/components/ui/dropdown-menu.tsx.hbs +262 -0
  51. package/templates/frontend/react/web-base/src/components/ui/input.tsx.hbs +20 -0
  52. package/templates/frontend/react/web-base/src/components/ui/label.tsx.hbs +20 -0
  53. package/templates/frontend/react/web-base/src/components/ui/skeleton.tsx.hbs +13 -0
  54. package/templates/frontend/react/web-base/src/components/ui/sonner.tsx.hbs +44 -0
  55. package/templates/frontend/react/web-base/src/index.css.hbs +58 -64
  56. package/templates/auth/better-auth/convex/backend/convex/convex.config.ts.hbs +0 -7
  57. package/templates/examples/ai/web/react/base/src/components/response.tsx.hbs +0 -22
  58. package/templates/frontend/react/web-base/src/components/ui/button.tsx +0 -56
  59. package/templates/frontend/react/web-base/src/components/ui/card.tsx +0 -75
  60. package/templates/frontend/react/web-base/src/components/ui/checkbox.tsx +0 -27
  61. package/templates/frontend/react/web-base/src/components/ui/dropdown-menu.tsx +0 -228
  62. package/templates/frontend/react/web-base/src/components/ui/input.tsx +0 -21
  63. package/templates/frontend/react/web-base/src/components/ui/label.tsx +0 -19
  64. package/templates/frontend/react/web-base/src/components/ui/skeleton.tsx +0 -13
  65. package/templates/frontend/react/web-base/src/components/ui/sonner.tsx +0 -25
  66. /package/templates/auth/better-auth/web/react/tanstack-router/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
  67. /package/templates/auth/better-auth/web/react/tanstack-router/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
  68. /package/templates/auth/better-auth/web/react/tanstack-router/src/routes/{login.tsx → login.tsx.hbs} +0 -0
  69. /package/templates/auth/better-auth/web/react/tanstack-start/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
  70. /package/templates/auth/better-auth/web/react/tanstack-start/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
  71. /package/templates/auth/better-auth/web/react/tanstack-start/src/routes/{login.tsx → login.tsx.hbs} +0 -0
  72. /package/templates/auth/better-auth/web/solid/src/components/{sign-in-form.tsx → sign-in-form.tsx.hbs} +0 -0
  73. /package/templates/auth/better-auth/web/solid/src/components/{sign-up-form.tsx → sign-up-form.tsx.hbs} +0 -0
  74. /package/templates/auth/better-auth/web/solid/src/routes/{login.tsx → login.tsx.hbs} +0 -0
  75. /package/templates/frontend/react/react-router/src/components/{theme-provider.tsx → theme-provider.tsx.hbs} +0 -0
  76. /package/templates/frontend/react/tanstack-router/src/components/{theme-provider.tsx → theme-provider.tsx.hbs} +0 -0
  77. /package/templates/frontend/react/web-base/src/lib/{utils.ts → utils.ts.hbs} +0 -0
@@ -1,11 +1,156 @@
1
+ {{#if (eq backend "convex")}}
2
+ import { api } from "@{{projectName}}/backend/convex/_generated/api";
3
+ import {
4
+ useUIMessages,
5
+ useSmoothText,
6
+ type UIMessage,
7
+ } from "@convex-dev/agent/react";
8
+ import { createFileRoute } from "@tanstack/react-router";
9
+ import { useMutation } from "convex/react";
10
+ import { Send, Loader2 } from "lucide-react";
11
+ import { useRef, useEffect, useState } from "react";
12
+ import { Streamdown } from "streamdown";
13
+
14
+ import { Button } from "@/components/ui/button";
15
+ import { Input } from "@/components/ui/input";
16
+
17
+ export const Route = createFileRoute("/ai")({
18
+ component: RouteComponent,
19
+ });
20
+
21
+ function MessageContent({
22
+ text,
23
+ isStreaming,
24
+ }: {
25
+ text: string;
26
+ isStreaming: boolean;
27
+ }) {
28
+ const [visibleText] = useSmoothText(text, {
29
+ startStreaming: isStreaming,
30
+ });
31
+ return <Streamdown>{visibleText}</Streamdown>;
32
+ }
33
+
34
+ function RouteComponent() {
35
+ const [input, setInput] = useState("");
36
+ const [threadId, setThreadId] = useState<string | null>(null);
37
+ const [isLoading, setIsLoading] = useState(false);
38
+ const messagesEndRef = useRef<HTMLDivElement>(null);
39
+
40
+ const createThread = useMutation(api.chat.createNewThread);
41
+ const sendMessage = useMutation(api.chat.sendMessage);
42
+
43
+ const { results: messages } = useUIMessages(
44
+ api.chat.listMessages,
45
+ threadId ? { threadId } : "skip",
46
+ { initialNumItems: 50, stream: true },
47
+ );
48
+
49
+ useEffect(() => {
50
+ messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
51
+ }, [messages]);
52
+
53
+ const hasStreamingMessage = messages?.some(
54
+ (m: UIMessage) => m.status === "streaming",
55
+ );
56
+
57
+ const handleSubmit = async (e: React.FormEvent<HTMLFormElement>) => {
58
+ e.preventDefault();
59
+ const text = input.trim();
60
+ if (!text || isLoading) return;
61
+
62
+ setIsLoading(true);
63
+ setInput("");
64
+
65
+ try {
66
+ let currentThreadId = threadId;
67
+ if (!currentThreadId) {
68
+ currentThreadId = await createThread();
69
+ setThreadId(currentThreadId);
70
+ }
71
+
72
+ await sendMessage({ threadId: currentThreadId, prompt: text });
73
+ } catch (error) {
74
+ console.error("Failed to send message:", error);
75
+ } finally {
76
+ setIsLoading(false);
77
+ }
78
+ };
79
+
80
+ return (
81
+ <div className="grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4">
82
+ <div className="overflow-y-auto space-y-4 pb-4">
83
+ {!messages || messages.length === 0 ? (
84
+ <div className="text-center text-muted-foreground mt-8">
85
+ Ask me anything to get started!
86
+ </div>
87
+ ) : (
88
+ messages.map((message: UIMessage) => (
89
+ <div
90
+ key={message.key}
91
+ className={`p-3 rounded-lg ${
92
+ message.role === "user"
93
+ ? "bg-primary/10 ml-8"
94
+ : "bg-secondary/20 mr-8"
95
+ }`}
96
+ >
97
+ <p className="text-sm font-semibold mb-1">
98
+ {message.role === "user" ? "You" : "AI Assistant"}
99
+ </p>
100
+ <MessageContent
101
+ text={message.text ?? ""}
102
+ isStreaming={message.status === "streaming"}
103
+ />
104
+ </div>
105
+ ))
106
+ )}
107
+ {isLoading && !hasStreamingMessage && (
108
+ <div className="p-3 rounded-lg bg-secondary/20 mr-8">
109
+ <p className="text-sm font-semibold mb-1">AI Assistant</p>
110
+ <div className="flex items-center gap-2 text-muted-foreground">
111
+ <Loader2 className="h-4 w-4 animate-spin" />
112
+ <span>Thinking...</span>
113
+ </div>
114
+ </div>
115
+ )}
116
+ <div ref={messagesEndRef} />
117
+ </div>
118
+
119
+ <form
120
+ onSubmit={handleSubmit}
121
+ className="w-full flex items-center space-x-2 pt-2 border-t"
122
+ >
123
+ <Input
124
+ name="prompt"
125
+ value={input}
126
+ onChange={(e) => setInput(e.target.value)}
127
+ placeholder="Type your message..."
128
+ className="flex-1"
129
+ autoComplete="off"
130
+ autoFocus
131
+ disabled={isLoading}
132
+ />
133
+ <Button type="submit" size="icon" disabled={isLoading || !input.trim()}>
134
+ {isLoading ? (
135
+ <Loader2 className="h-4 w-4 animate-spin" />
136
+ ) : (
137
+ <Send size={18} />
138
+ )}
139
+ </Button>
140
+ </form>
141
+ </div>
142
+ );
143
+ }
144
+ {{else}}
1
145
  import { createFileRoute } from "@tanstack/react-router";
2
146
  import { useChat } from "@ai-sdk/react";
3
147
  import { DefaultChatTransport } from "ai";
4
- import { Input } from "@/components/ui/input";
5
- import { Button } from "@/components/ui/button";
6
148
  import { Send } from "lucide-react";
7
149
  import { useRef, useEffect, useState } from "react";
8
- import { Response } from "@/components/response";
150
+ import { Streamdown } from "streamdown";
151
+
152
+ import { Button } from "@/components/ui/button";
153
+ import { Input } from "@/components/ui/input";
9
154
 
10
155
  export const Route = createFileRoute("/ai")({
11
156
  component: RouteComponent,
@@ -13,7 +158,7 @@ export const Route = createFileRoute("/ai")({
13
158
 
14
159
  function RouteComponent() {
15
160
  const [input, setInput] = useState("");
16
- const { messages, sendMessage } = useChat({
161
+ const { messages, sendMessage, status } = useChat({
17
162
  transport: new DefaultChatTransport({
18
163
  api: {{#if (eq backend "self")}}"/api/ai"{{else}}`${import.meta.env.VITE_SERVER_URL}/ai`{{/if}},
19
164
  }),
@@ -55,7 +200,14 @@ function RouteComponent() {
55
200
  </p>
56
201
  {message.parts?.map((part, index) => {
57
202
  if (part.type === "text") {
58
- return <Response key={index}>{part.text}</Response>;
203
+ return (
204
+ <Streamdown
205
+ key={index}
206
+ isAnimating={status === "streaming" && message.role === "assistant"}
207
+ >
208
+ {part.text}
209
+ </Streamdown>
210
+ );
59
211
  }
60
212
  return null;
61
213
  })}
@@ -84,4 +236,5 @@ function RouteComponent() {
84
236
  </form>
85
237
  </div>
86
238
  );
87
- }
239
+ }
240
+ {{/if}}
@@ -3,20 +3,21 @@
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
5
  "scripts": {
6
- "dev": "next dev",
6
+ "dev": "next dev --port 3001",
7
7
  "build": "next build",
8
8
  "start": "next start"
9
9
  },
10
10
  "dependencies": {
11
- "radix-ui": "^1.4.2",
11
+ "@base-ui/react": "^1.0.0",
12
+ "shadcn": "^3.6.2",
12
13
  "@tanstack/react-form": "^1.27.3",
13
14
  "class-variance-authority": "^0.7.1",
14
15
  "clsx": "^2.1.1",
15
16
  "lucide-react": "^0.546.0",
16
- "next": "^16.0.10",
17
+ "next": "^16.1.0",
17
18
  "next-themes": "^0.4.6",
18
- "react": "19.2.3",
19
- "react-dom": "19.2.3",
19
+ "react": "^19.2.3",
20
+ "react-dom": "^19.2.3",
20
21
  "sonner": "^2.0.5",
21
22
  "tailwind-merge": "^3.3.1",
22
23
  "tw-animate-css": "^1.3.4",
@@ -25,8 +26,8 @@
25
26
  "devDependencies": {
26
27
  "@tailwindcss/postcss": "^4.1.10",
27
28
  "@types/node": "^20",
28
- "@types/react": "19.2.7",
29
- "@types/react-dom": "19.2.3",
29
+ "@types/react": "^19.2.7",
30
+ "@types/react-dom": "^19.2.3",
30
31
  "tailwindcss": "^4.1.10",
31
32
  "typescript": "^5"
32
33
  }
@@ -2,7 +2,10 @@ import type { Metadata } from "next";
2
2
  import { Geist, Geist_Mono } from "next/font/google";
3
3
  import "../index.css";
4
4
  {{#if (eq auth "clerk")}}{{#if (eq backend "convex")}}import { ClerkProvider } from "@clerk/nextjs";
5
- {{/if}}{{/if}}import Providers from "@/components/providers";
5
+ {{/if}}{{/if}}{{#if (and (eq backend "convex") (eq auth "better-auth"))}}
6
+ import { getToken } from "@/lib/auth-server";
7
+ {{/if}}
8
+ import Providers from "@/components/providers";
6
9
  import Header from "@/components/header";
7
10
 
8
11
  const geistSans = Geist({
@@ -20,6 +23,29 @@ export const metadata: Metadata = {
20
23
  description: "{{projectName}}",
21
24
  };
22
25
 
26
+ {{#if (and (eq backend "convex") (eq auth "better-auth"))}}
27
+ export default async function RootLayout({
28
+ children,
29
+ }: Readonly<{
30
+ children: React.ReactNode;
31
+ }>) {
32
+ const token = await getToken();
33
+ return (
34
+ <html lang="en" suppressHydrationWarning>
35
+ <body
36
+ className={`${geistSans.variable} ${geistMono.variable} antialiased`}
37
+ >
38
+ <Providers initialToken={token}>
39
+ <div className="grid grid-rows-[auto_1fr] h-svh">
40
+ <Header />
41
+ {children}
42
+ </div>
43
+ </Providers>
44
+ </body>
45
+ </html>
46
+ );
47
+ }
48
+ {{else}}
23
49
  export default function RootLayout({
24
50
  children,
25
51
  }: Readonly<{
@@ -47,3 +73,4 @@ export default function RootLayout({
47
73
  </html>
48
74
  );
49
75
  }
76
+ {{/if}}
@@ -16,12 +16,10 @@ export function ModeToggle() {
16
16
 
17
17
  return (
18
18
  <DropdownMenu>
19
- <DropdownMenuTrigger asChild>
20
- <Button variant="outline" size="icon">
21
- <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
22
- <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
23
- <span className="sr-only">Toggle theme</span>
24
- </Button>
19
+ <DropdownMenuTrigger render={<Button variant="outline" size="icon" />}>
20
+ <Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
21
+ <Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
22
+ <span className="sr-only">Toggle theme</span>
25
23
  </DropdownMenuTrigger>
26
24
  <DropdownMenuContent align="end">
27
25
  <DropdownMenuItem onClick={() => setTheme("light")}>
@@ -6,7 +6,7 @@ import { useAuth } from "@clerk/nextjs";
6
6
  import { ConvexReactClient } from "convex/react";
7
7
  import { ConvexProviderWithClerk } from "convex/react-clerk";
8
8
  {{else if (eq auth "better-auth")}}
9
- import { ConvexProvider, ConvexReactClient } from "convex/react";
9
+ import { ConvexReactClient } from "convex/react";
10
10
  import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react";
11
11
  import { authClient } from "@/lib/auth-client";
12
12
  {{else}}
@@ -32,9 +32,15 @@ const convex = new ConvexReactClient(process.env.NEXT_PUBLIC_CONVEX_URL!);
32
32
  {{/if}}
33
33
 
34
34
  export default function Providers({
35
- children
35
+ children,
36
+ {{#if (and (eq backend "convex") (eq auth "better-auth"))}}
37
+ initialToken,
38
+ {{/if}}
36
39
  }: {
37
- children: React.ReactNode
40
+ children: React.ReactNode;
41
+ {{#if (and (eq backend "convex") (eq auth "better-auth"))}}
42
+ initialToken?: string | null;
43
+ {{/if}}
38
44
  }) {
39
45
  return (
40
46
  <ThemeProvider
@@ -49,7 +55,11 @@ export default function Providers({
49
55
  {children}
50
56
  </ConvexProviderWithClerk>
51
57
  {{else if (eq auth "better-auth")}}
52
- <ConvexBetterAuthProvider client={convex} authClient={authClient}>
58
+ <ConvexBetterAuthProvider
59
+ client={convex}
60
+ authClient={authClient}
61
+ initialToken={initialToken}
62
+ >
53
63
  {children}
54
64
  </ConvexBetterAuthProvider>
55
65
  {{else}}
@@ -9,7 +9,8 @@
9
9
  "typecheck": "react-router typegen && tsc"
10
10
  },
11
11
  "dependencies": {
12
- "radix-ui": "^1.4.2",
12
+ "@base-ui/react": "^1.0.0",
13
+ "shadcn": "^3.6.2",
13
14
  "@react-router/fs-routes": "^7.10.1",
14
15
  "@react-router/node": "^7.10.1",
15
16
  "@react-router/serve": "^7.10.1",
@@ -14,12 +14,10 @@ export function ModeToggle() {
14
14
 
15
15
  return (
16
16
  <DropdownMenu>
17
- <DropdownMenuTrigger asChild>
18
- <Button variant="outline" size="icon">
19
- <Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
20
- <Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
21
- <span className="sr-only">Toggle theme</span>
22
- </Button>
17
+ <DropdownMenuTrigger render={<Button variant="outline" size="icon" />}>
18
+ <Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
19
+ <Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
20
+ <span className="sr-only">Toggle theme</span>
23
21
  </DropdownMenuTrigger>
24
22
  <DropdownMenuContent align="end">
25
23
  <DropdownMenuItem onClick={() => setTheme("light")}>Light</DropdownMenuItem>
@@ -12,7 +12,8 @@
12
12
  },
13
13
  "dependencies": {
14
14
  "@hookform/resolvers": "^5.1.1",
15
- "radix-ui": "^1.4.2",
15
+ "@base-ui/react": "^1.0.0",
16
+ "shadcn": "^3.6.2",
16
17
  "@tanstack/react-form": "^1.12.3",
17
18
  "@tailwindcss/vite": "^4.0.15",
18
19
  "@tanstack/react-router": "^1.141.1",
@@ -14,12 +14,10 @@ export function ModeToggle() {
14
14
 
15
15
  return (
16
16
  <DropdownMenu>
17
- <DropdownMenuTrigger asChild>
18
- <Button variant="outline" size="icon">
19
- <Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
20
- <Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
21
- <span className="sr-only">Toggle theme</span>
22
- </Button>
17
+ <DropdownMenuTrigger render={<Button variant="outline" size="icon" />}>
18
+ <Sun className="h-[1.2rem] w-[1.2rem] scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
19
+ <Moon className="absolute h-[1.2rem] w-[1.2rem] scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
20
+ <span className="sr-only">Toggle theme</span>
23
21
  </DropdownMenuTrigger>
24
22
  <DropdownMenuContent align="end">
25
23
  <DropdownMenuItem onClick={() => setTheme("light")}>Light</DropdownMenuItem>
@@ -8,7 +8,8 @@
8
8
  "dev": "vite dev"
9
9
  },
10
10
  "dependencies": {
11
- "radix-ui": "^1.4.2",
11
+ "@base-ui/react": "^1.0.0",
12
+ "shadcn": "^3.6.2",
12
13
  "@tanstack/react-form": "^1.23.5",
13
14
  "@tailwindcss/vite": "^4.1.8",
14
15
  "@tanstack/react-query": "^5.80.6",
@@ -35,7 +35,13 @@ export function getRouter() {
35
35
  unsavedChangesWarning: false,
36
36
  });
37
37
 
38
+ {{#if (eq auth "better-auth")}}
39
+ const convexQueryClient = new ConvexQueryClient(convex, {
40
+ expectAuth: true,
41
+ });
42
+ {{else}}
38
43
  const convexQueryClient = new ConvexQueryClient(convex);
44
+ {{/if}}
39
45
 
40
46
  const queryClient: QueryClient = new QueryClient({
41
47
  defaultOptions: {
@@ -37,20 +37,12 @@ const fetchClerkAuth = createServerFn({ method: "GET" }).handler(async () => {
37
37
  });
38
38
  {{else if (and (eq backend "convex") (eq auth "better-auth"))}}
39
39
  import { createServerFn } from "@tanstack/react-start";
40
- import { getRequest, getCookie } from "@tanstack/react-start/server";
41
40
  import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react";
42
- import { fetchSession, getCookieName } from "@convex-dev/better-auth/react-start";
43
41
  import { authClient } from "@/lib/auth-client";
44
- import { createAuth } from "@{{projectName}}/backend/convex/auth";
42
+ import { getToken } from "@/lib/auth-server";
45
43
 
46
- const fetchAuth = createServerFn({ method: "GET" }).handler(async () => {
47
- const { session } = await fetchSession(getRequest());
48
- const sessionCookieName = getCookieName(createAuth);
49
- const token = getCookie(sessionCookieName);
50
- return {
51
- userId: session?.user.id,
52
- token,
53
- };
44
+ const getAuth = createServerFn({ method: "GET" }).handler(async () => {
45
+ return await getToken();
54
46
  });
55
47
  {{/if}}
56
48
 
@@ -113,11 +105,14 @@ export const Route = createRootRouteWithContext<RouterAppContext>()({
113
105
  },
114
106
  {{else if (and (eq backend "convex") (eq auth "better-auth"))}}
115
107
  beforeLoad: async (ctx) => {
116
- const { userId, token } = await fetchAuth();
108
+ const token = await getAuth();
117
109
  if (token) {
118
110
  ctx.context.convexQueryClient.serverHttpClient?.setAuth(token);
119
111
  }
120
- return { userId, token };
112
+ return {
113
+ isAuthenticated: !!token,
114
+ token,
115
+ };
121
116
  },
122
117
  {{/if}}
123
118
  });
@@ -148,7 +143,11 @@ function RootDocument() {
148
143
  {{else if (and (eq backend "convex") (eq auth "better-auth"))}}
149
144
  const context = useRouteContext({ from: Route.id });
150
145
  return (
151
- <ConvexBetterAuthProvider client={context.convexClient} authClient={authClient}>
146
+ <ConvexBetterAuthProvider
147
+ client={context.convexClient}
148
+ authClient={authClient}
149
+ initialToken={context.token}
150
+ >
152
151
  <html lang="en" className="dark">
153
152
  <head>
154
153
  <HeadContent />
@@ -14,4 +14,9 @@ export default defineConfig({
14
14
  server: {
15
15
  port: 3001,
16
16
  },
17
+ {{#if (and (eq backend "convex") (eq auth "better-auth"))}}
18
+ ssr: {
19
+ noExternal: ["@convex-dev/better-auth"],
20
+ },
21
+ {{/if}}
17
22
  });
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "$schema": "https://ui.shadcn.com/schema.json",
3
- "style": "new-york",
3
+ "style": "base-lyra",
4
4
  "rsc": false,
5
5
  "tsx": true,
6
6
  "tailwind": {
@@ -10,6 +10,7 @@
10
10
  "cssVariables": true,
11
11
  "prefix": ""
12
12
  },
13
+ "iconLibrary": "lucide",
13
14
  "aliases": {
14
15
  "components": "@/components",
15
16
  "utils": "@/lib/utils",
@@ -17,5 +18,7 @@
17
18
  "lib": "@/lib",
18
19
  "hooks": "@/hooks"
19
20
  },
20
- "iconLibrary": "lucide"
21
+ "menuColor": "default",
22
+ "menuAccent": "subtle",
23
+ "registries": {}
21
24
  }
@@ -0,0 +1,57 @@
1
+ import { Button as ButtonPrimitive } from '@base-ui/react/button'
2
+ import { cva } from 'class-variance-authority'
3
+ import type {VariantProps} from 'class-variance-authority';
4
+
5
+ import { cn } from '@/lib/utils'
6
+
7
+ const buttonVariants = cva(
8
+ "focus-visible:border-ring focus-visible:ring-ring/50 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:aria-invalid:border-destructive/50 rounded-none border border-transparent bg-clip-padding text-xs font-medium focus-visible:ring-1 aria-invalid:ring-1 [&_svg:not([class*='size-'])]:size-4 inline-flex items-center justify-center whitespace-nowrap transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none shrink-0 [&_svg]:shrink-0 outline-none group/button select-none",
9
+ {
10
+ variants: {
11
+ variant: {
12
+ default: 'bg-primary text-primary-foreground [a]:hover:bg-primary/80',
13
+ outline:
14
+ 'border-border bg-background hover:bg-muted hover:text-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50 aria-expanded:bg-muted aria-expanded:text-foreground',
15
+ secondary:
16
+ 'bg-secondary text-secondary-foreground hover:bg-secondary/80 aria-expanded:bg-secondary aria-expanded:text-secondary-foreground',
17
+ ghost:
18
+ 'hover:bg-muted hover:text-foreground dark:hover:bg-muted/50 aria-expanded:bg-muted aria-expanded:text-foreground',
19
+ destructive:
20
+ 'bg-destructive/10 hover:bg-destructive/20 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/20 text-destructive focus-visible:border-destructive/40 dark:hover:bg-destructive/30',
21
+ link: 'text-primary underline-offset-4 hover:underline',
22
+ },
23
+ size: {
24
+ default:
25
+ 'h-8 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-2 has-data-[icon=inline-start]:pl-2',
26
+ xs: "h-6 gap-1 rounded-none px-2 text-xs has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3",
27
+ sm: "h-7 gap-1 rounded-none px-2.5 has-data-[icon=inline-end]:pr-1.5 has-data-[icon=inline-start]:pl-1.5 [&_svg:not([class*='size-'])]:size-3.5",
28
+ lg: 'h-9 gap-1.5 px-2.5 has-data-[icon=inline-end]:pr-3 has-data-[icon=inline-start]:pl-3',
29
+ icon: 'size-8',
30
+ 'icon-xs': "size-6 rounded-none [&_svg:not([class*='size-'])]:size-3",
31
+ 'icon-sm': 'size-7 rounded-none',
32
+ 'icon-lg': 'size-9',
33
+ },
34
+ },
35
+ defaultVariants: {
36
+ variant: 'default',
37
+ size: 'default',
38
+ },
39
+ },
40
+ )
41
+
42
+ function Button({
43
+ className,
44
+ variant = 'default',
45
+ size = 'default',
46
+ ...props
47
+ }: ButtonPrimitive.Props & VariantProps<typeof buttonVariants>) {
48
+ return (
49
+ <ButtonPrimitive
50
+ data-slot="button"
51
+ className={cn(buttonVariants({ variant, size, className }))}
52
+ {...props}
53
+ />
54
+ )
55
+ }
56
+
57
+ export { Button, buttonVariants }
@@ -0,0 +1,103 @@
1
+ import * as React from 'react'
2
+
3
+ import { cn } from '@/lib/utils'
4
+
5
+ function Card({
6
+ className,
7
+ size = 'default',
8
+ ...props
9
+ }: React.ComponentProps<'div'> & { size?: 'default' | 'sm' }) {
10
+ return (
11
+ <div
12
+ data-slot="card"
13
+ data-size={size}
14
+ className={cn(
15
+ 'ring-foreground/10 bg-card text-card-foreground gap-4 overflow-hidden rounded-none py-4 text-xs/relaxed ring-1 has-data-[slot=card-footer]:pb-0 has-[>img:first-child]:pt-0 data-[size=sm]:gap-2 data-[size=sm]:py-3 data-[size=sm]:has-data-[slot=card-footer]:pb-0 *:[img:first-child]:rounded-none *:[img:last-child]:rounded-none group/card flex flex-col',
16
+ className,
17
+ )}
18
+ {...props}
19
+ />
20
+ )
21
+ }
22
+
23
+ function CardHeader({ className, ...props }: React.ComponentProps<'div'>) {
24
+ return (
25
+ <div
26
+ data-slot="card-header"
27
+ className={cn(
28
+ 'gap-1 rounded-none px-4 group-data-[size=sm]/card:px-3 [.border-b]:pb-4 group-data-[size=sm]/card:[.border-b]:pb-3 group/card-header @container/card-header grid auto-rows-min items-start has-data-[slot=card-action]:grid-cols-[1fr_auto] has-data-[slot=card-description]:grid-rows-[auto_auto]',
29
+ className,
30
+ )}
31
+ {...props}
32
+ />
33
+ )
34
+ }
35
+
36
+ function CardTitle({ className, ...props }: React.ComponentProps<'div'>) {
37
+ return (
38
+ <div
39
+ data-slot="card-title"
40
+ className={cn(
41
+ 'text-sm font-medium group-data-[size=sm]/card:text-sm',
42
+ className,
43
+ )}
44
+ {...props}
45
+ />
46
+ )
47
+ }
48
+
49
+ function CardDescription({ className, ...props }: React.ComponentProps<'div'>) {
50
+ return (
51
+ <div
52
+ data-slot="card-description"
53
+ className={cn('text-muted-foreground text-xs/relaxed', className)}
54
+ {...props}
55
+ />
56
+ )
57
+ }
58
+
59
+ function CardAction({ className, ...props }: React.ComponentProps<'div'>) {
60
+ return (
61
+ <div
62
+ data-slot="card-action"
63
+ className={cn(
64
+ 'col-start-2 row-span-2 row-start-1 self-start justify-self-end',
65
+ className,
66
+ )}
67
+ {...props}
68
+ />
69
+ )
70
+ }
71
+
72
+ function CardContent({ className, ...props }: React.ComponentProps<'div'>) {
73
+ return (
74
+ <div
75
+ data-slot="card-content"
76
+ className={cn('px-4 group-data-[size=sm]/card:px-3', className)}
77
+ {...props}
78
+ />
79
+ )
80
+ }
81
+
82
+ function CardFooter({ className, ...props }: React.ComponentProps<'div'>) {
83
+ return (
84
+ <div
85
+ data-slot="card-footer"
86
+ className={cn(
87
+ 'rounded-none border-t p-4 group-data-[size=sm]/card:p-3 flex items-center',
88
+ className,
89
+ )}
90
+ {...props}
91
+ />
92
+ )
93
+ }
94
+
95
+ export {
96
+ Card,
97
+ CardHeader,
98
+ CardFooter,
99
+ CardTitle,
100
+ CardAction,
101
+ CardDescription,
102
+ CardContent,
103
+ }