create-better-t-stack 2.0.11 → 2.1.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 (125) hide show
  1. package/dist/index.js +81 -81
  2. package/package.json +1 -1
  3. package/templates/api/orpc/native/utils/orpc.ts.hbs +49 -0
  4. package/templates/api/orpc/web/nuxt/app/plugins/orpc.ts.hbs +35 -0
  5. package/templates/{auth/native/utils/trpc.ts → api/trpc/native/utils/trpc.ts.hbs} +4 -0
  6. package/templates/api/trpc/web/{base → react/base}/src/utils/trpc.ts.hbs +1 -1
  7. package/templates/auth/native/app/(drawer)/{index.tsx → index.tsx.hbs} +11 -0
  8. package/templates/auth/native/components/{sign-in.tsx → sign-in.tsx.hbs} +6 -1
  9. package/templates/auth/native/components/{sign-up.tsx → sign-up.tsx.hbs} +5 -0
  10. package/templates/auth/server/base/src/lib/auth.ts.hbs +48 -9
  11. package/templates/auth/web/nuxt/app/components/SignInForm.vue +78 -0
  12. package/templates/auth/web/nuxt/app/components/SignUpForm.vue +85 -0
  13. package/templates/auth/web/nuxt/app/components/UserMenu.vue +43 -0
  14. package/templates/auth/web/nuxt/app/middleware/auth.ts +12 -0
  15. package/templates/auth/web/nuxt/app/pages/dashboard.vue +27 -0
  16. package/templates/auth/web/nuxt/app/pages/login.vue +24 -0
  17. package/templates/auth/web/nuxt/app/plugins/auth-client.ts +16 -0
  18. package/templates/backend/express/src/index.ts.hbs +2 -0
  19. package/templates/examples/ai/web/nuxt/app/pages/ai.vue +64 -0
  20. package/templates/examples/todo/web/nuxt/app/pages/todos.vue +108 -0
  21. package/templates/extras/_npmrc.hbs +5 -0
  22. package/templates/frontend/native/app/(drawer)/{index.tsx → index.tsx.hbs} +10 -0
  23. package/templates/frontend/native/app/{_layout.tsx → _layout.tsx.hbs} +8 -3
  24. package/templates/frontend/native/package.json +0 -3
  25. package/templates/frontend/nuxt/_gitignore +24 -0
  26. package/templates/frontend/nuxt/app/app.config.ts +15 -0
  27. package/templates/frontend/nuxt/app/app.vue +13 -0
  28. package/templates/frontend/nuxt/app/assets/css/main.css +2 -0
  29. package/templates/frontend/nuxt/app/components/Header.vue.hbs +45 -0
  30. package/templates/frontend/nuxt/app/components/Loader.vue +5 -0
  31. package/templates/frontend/nuxt/app/components/ModeToggle.vue +23 -0
  32. package/templates/frontend/nuxt/app/layouts/default.vue +11 -0
  33. package/templates/frontend/nuxt/app/pages/index.vue +63 -0
  34. package/templates/frontend/nuxt/app/plugins/vue-query.ts +44 -0
  35. package/templates/frontend/nuxt/nuxt.config.ts.hbs +19 -0
  36. package/templates/frontend/nuxt/package.json +25 -0
  37. package/templates/frontend/nuxt/public/favicon.ico +0 -0
  38. package/templates/frontend/nuxt/public/robots.txt +2 -0
  39. package/templates/frontend/nuxt/server/tsconfig.json +3 -0
  40. package/templates/frontend/nuxt/tsconfig.json +4 -0
  41. package/templates/frontend/{tanstack-start → react/tanstack-start}/package.json +1 -1
  42. package/templates/extras/_npmrc +0 -1
  43. package/templates/frontend/native/utils/trpc.ts +0 -19
  44. /package/templates/api/orpc/web/{base → react/base}/src/utils/orpc.ts.hbs +0 -0
  45. /package/templates/auth/web/{base → react/base}/src/lib/auth-client.ts.hbs +0 -0
  46. /package/templates/auth/web/{next → react/next}/src/app/dashboard/page.tsx.hbs +0 -0
  47. /package/templates/auth/web/{next → react/next}/src/app/login/page.tsx +0 -0
  48. /package/templates/auth/web/{next → react/next}/src/components/sign-in-form.tsx +0 -0
  49. /package/templates/auth/web/{next → react/next}/src/components/sign-up-form.tsx +0 -0
  50. /package/templates/auth/web/{next → react/next}/src/components/theme-provider.tsx +0 -0
  51. /package/templates/auth/web/{next → react/next}/src/components/user-menu.tsx +0 -0
  52. /package/templates/auth/web/{react-router → react/react-router}/src/components/sign-in-form.tsx +0 -0
  53. /package/templates/auth/web/{react-router → react/react-router}/src/components/sign-up-form.tsx +0 -0
  54. /package/templates/auth/web/{react-router → react/react-router}/src/components/user-menu.tsx +0 -0
  55. /package/templates/auth/web/{react-router → react/react-router}/src/routes/dashboard.tsx.hbs +0 -0
  56. /package/templates/auth/web/{react-router → react/react-router}/src/routes/login.tsx +0 -0
  57. /package/templates/auth/web/{tanstack-router → react/tanstack-router}/src/components/sign-in-form.tsx +0 -0
  58. /package/templates/auth/web/{tanstack-router → react/tanstack-router}/src/components/sign-up-form.tsx +0 -0
  59. /package/templates/auth/web/{tanstack-router → react/tanstack-router}/src/components/user-menu.tsx +0 -0
  60. /package/templates/auth/web/{tanstack-router → react/tanstack-router}/src/routes/dashboard.tsx.hbs +0 -0
  61. /package/templates/auth/web/{tanstack-router → react/tanstack-router}/src/routes/login.tsx +0 -0
  62. /package/templates/auth/web/{tanstack-start → react/tanstack-start}/src/components/sign-in-form.tsx +0 -0
  63. /package/templates/auth/web/{tanstack-start → react/tanstack-start}/src/components/sign-up-form.tsx +0 -0
  64. /package/templates/auth/web/{tanstack-start → react/tanstack-start}/src/components/user-menu.tsx +0 -0
  65. /package/templates/auth/web/{tanstack-start → react/tanstack-start}/src/routes/dashboard.tsx.hbs +0 -0
  66. /package/templates/auth/web/{tanstack-start → react/tanstack-start}/src/routes/login.tsx +0 -0
  67. /package/templates/examples/ai/web/{react-router → react/react-router}/src/routes/ai.tsx +0 -0
  68. /package/templates/examples/ai/web/{tanstack-router → react/tanstack-router}/src/routes/ai.tsx +0 -0
  69. /package/templates/examples/ai/web/{tanstack-start → react/tanstack-start}/src/routes/ai.tsx +0 -0
  70. /package/templates/examples/todo/web/{react-router → react/react-router}/src/routes/todos.tsx.hbs +0 -0
  71. /package/templates/examples/todo/web/{tanstack-router → react/tanstack-router}/src/routes/todos.tsx.hbs +0 -0
  72. /package/templates/examples/todo/web/{tanstack-start → react/tanstack-start}/src/routes/todos.tsx.hbs +0 -0
  73. /package/templates/frontend/{next → react/next}/next-env.d.ts +0 -0
  74. /package/templates/frontend/{next → react/next}/next.config.ts +0 -0
  75. /package/templates/frontend/{next → react/next}/package.json +0 -0
  76. /package/templates/frontend/{next → react/next}/postcss.config.mjs +0 -0
  77. /package/templates/frontend/{next → react/next}/src/app/favicon.ico +0 -0
  78. /package/templates/frontend/{next → react/next}/src/app/layout.tsx +0 -0
  79. /package/templates/frontend/{next → react/next}/src/app/page.tsx.hbs +0 -0
  80. /package/templates/frontend/{next → react/next}/src/components/mode-toggle.tsx +0 -0
  81. /package/templates/frontend/{next → react/next}/src/components/providers.tsx.hbs +0 -0
  82. /package/templates/frontend/{next → react/next}/src/components/theme-provider.tsx +0 -0
  83. /package/templates/frontend/{next → react/next}/tsconfig.json +0 -0
  84. /package/templates/frontend/{react-router → react/react-router}/package.json +0 -0
  85. /package/templates/frontend/{react-router → react/react-router}/public/favicon.ico +0 -0
  86. /package/templates/frontend/{react-router → react/react-router}/react-router.config.ts +0 -0
  87. /package/templates/frontend/{react-router → react/react-router}/src/components/mode-toggle.tsx +0 -0
  88. /package/templates/frontend/{react-router → react/react-router}/src/components/theme-provider.tsx +0 -0
  89. /package/templates/frontend/{react-router → react/react-router}/src/root.tsx.hbs +0 -0
  90. /package/templates/frontend/{react-router → react/react-router}/src/routes/_index.tsx.hbs +0 -0
  91. /package/templates/frontend/{react-router → react/react-router}/src/routes.ts +0 -0
  92. /package/templates/frontend/{react-router → react/react-router}/tsconfig.json +0 -0
  93. /package/templates/frontend/{react-router → react/react-router}/vite.config.ts.hbs +0 -0
  94. /package/templates/frontend/{tanstack-router → react/tanstack-router}/index.html +0 -0
  95. /package/templates/frontend/{tanstack-router → react/tanstack-router}/package.json +0 -0
  96. /package/templates/frontend/{tanstack-router → react/tanstack-router}/src/components/mode-toggle.tsx +0 -0
  97. /package/templates/frontend/{tanstack-router → react/tanstack-router}/src/components/theme-provider.tsx +0 -0
  98. /package/templates/frontend/{tanstack-router → react/tanstack-router}/src/main.tsx.hbs +0 -0
  99. /package/templates/frontend/{tanstack-router → react/tanstack-router}/src/routes/__root.tsx.hbs +0 -0
  100. /package/templates/frontend/{tanstack-router → react/tanstack-router}/src/routes/index.tsx.hbs +0 -0
  101. /package/templates/frontend/{tanstack-router → react/tanstack-router}/tsconfig.json +0 -0
  102. /package/templates/frontend/{tanstack-router → react/tanstack-router}/vite.config.ts.hbs +0 -0
  103. /package/templates/frontend/{tanstack-start → react/tanstack-start}/app.config.ts +0 -0
  104. /package/templates/frontend/{tanstack-start → react/tanstack-start}/public/robots.txt +0 -0
  105. /package/templates/frontend/{tanstack-start → react/tanstack-start}/src/api.ts +0 -0
  106. /package/templates/frontend/{tanstack-start → react/tanstack-start}/src/client.tsx +0 -0
  107. /package/templates/frontend/{tanstack-start → react/tanstack-start}/src/router.tsx.hbs +0 -0
  108. /package/templates/frontend/{tanstack-start → react/tanstack-start}/src/routes/__root.tsx.hbs +0 -0
  109. /package/templates/frontend/{tanstack-start → react/tanstack-start}/src/routes/index.tsx.hbs +0 -0
  110. /package/templates/frontend/{tanstack-start → react/tanstack-start}/src/ssr.tsx +0 -0
  111. /package/templates/frontend/{tanstack-start → react/tanstack-start}/tsconfig.json +0 -0
  112. /package/templates/frontend/{web-base → react/web-base}/_gitignore +0 -0
  113. /package/templates/frontend/{web-base → react/web-base}/components.json +0 -0
  114. /package/templates/frontend/{web-base → react/web-base}/src/components/header.tsx.hbs +0 -0
  115. /package/templates/frontend/{web-base → react/web-base}/src/components/loader.tsx +0 -0
  116. /package/templates/frontend/{web-base → react/web-base}/src/components/ui/button.tsx +0 -0
  117. /package/templates/frontend/{web-base → react/web-base}/src/components/ui/card.tsx +0 -0
  118. /package/templates/frontend/{web-base → react/web-base}/src/components/ui/checkbox.tsx +0 -0
  119. /package/templates/frontend/{web-base → react/web-base}/src/components/ui/dropdown-menu.tsx +0 -0
  120. /package/templates/frontend/{web-base → react/web-base}/src/components/ui/input.tsx +0 -0
  121. /package/templates/frontend/{web-base → react/web-base}/src/components/ui/label.tsx +0 -0
  122. /package/templates/frontend/{web-base → react/web-base}/src/components/ui/skeleton.tsx +0 -0
  123. /package/templates/frontend/{web-base → react/web-base}/src/components/ui/sonner.tsx +0 -0
  124. /package/templates/frontend/{web-base → react/web-base}/src/index.css +0 -0
  125. /package/templates/frontend/{web-base → react/web-base}/src/lib/utils.ts +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "2.0.11",
3
+ "version": "2.1.0",
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,49 @@
1
+ import { createORPCClient } from "@orpc/client";
2
+ import { RPCLink } from "@orpc/client/fetch";
3
+ import { createORPCReactQueryUtils } from "@orpc/react-query";
4
+ import type { RouterUtils } from "@orpc/react-query";
5
+ import type { RouterClient } from "@orpc/server";
6
+ import { QueryCache, QueryClient } from "@tanstack/react-query";
7
+ import { createContext, useContext } from "react";
8
+ import type { appRouter } from "../../server/src/routers";
9
+ {{#if auth}}
10
+ import { authClient } from "@/lib/auth-client";
11
+ {{/if}}
12
+
13
+ type ORPCReactUtils = RouterUtils<RouterClient<typeof appRouter>>;
14
+
15
+ export const queryClient = new QueryClient({
16
+ queryCache: new QueryCache({
17
+ onError: (error) => {
18
+ console.log(error)
19
+ },
20
+ }),
21
+ });
22
+
23
+ export const link = new RPCLink({
24
+ url: `${process.env.EXPO_PUBLIC_SERVER_URL}/rpc`,
25
+ {{#if auth}}
26
+ headers() {
27
+ const headers = new Map<string, string>();
28
+ const cookies = authClient.getCookie();
29
+ if (cookies) {
30
+ headers.set("Cookie", cookies);
31
+ }
32
+ return Object.fromEntries(headers);
33
+ },
34
+ {{/if}}
35
+ });
36
+
37
+ export const client: RouterClient<typeof appRouter> = createORPCClient(link);
38
+
39
+ export const orpc = createORPCReactQueryUtils(client);
40
+
41
+ export const ORPCContext = createContext<ORPCReactUtils | undefined>(undefined);
42
+
43
+ export function useORPC(): ORPCReactUtils {
44
+ const orpc = useContext(ORPCContext);
45
+ if (!orpc) {
46
+ throw new Error("ORPCContext is not set up properly");
47
+ }
48
+ return orpc;
49
+ }
@@ -0,0 +1,35 @@
1
+ import { defineNuxtPlugin, useRuntimeConfig } from '#app'
2
+ import type { RouterClient } from '@orpc/server'
3
+ import type { appRouter } from "../../../server/src/routers/index";
4
+ import { createORPCClient } from '@orpc/client'
5
+ import { RPCLink } from '@orpc/client/fetch'
6
+ import { createORPCVueQueryUtils } from '@orpc/vue-query'
7
+
8
+ export default defineNuxtPlugin(() => {
9
+ const config = useRuntimeConfig()
10
+ const serverUrl = config.public.serverURL
11
+
12
+ const rpcUrl = `${serverUrl}/rpc`;
13
+
14
+ const rpcLink = new RPCLink({
15
+ url: rpcUrl,
16
+ {{#if auth}}
17
+ fetch(url, options) {
18
+ return fetch(url, {
19
+ ...options,
20
+ credentials: "include",
21
+ });
22
+ },
23
+ {{/if}}
24
+ })
25
+
26
+
27
+ const client: RouterClient<typeof appRouter> = createORPCClient(rpcLink)
28
+ const orpcUtils = createORPCVueQueryUtils(client)
29
+
30
+ return {
31
+ provide: {
32
+ orpc: orpcUtils
33
+ }
34
+ }
35
+ })
@@ -1,4 +1,6 @@
1
+ {{#if auth}}
1
2
  import { authClient } from "@/lib/auth-client";
3
+ {{/if}}
2
4
  import { QueryClient } from "@tanstack/react-query";
3
5
  import { createTRPCClient, httpBatchLink } from "@trpc/client";
4
6
  import { createTRPCOptionsProxy } from "@trpc/tanstack-react-query";
@@ -10,6 +12,7 @@ const trpcClient = createTRPCClient<AppRouter>({
10
12
  links: [
11
13
  httpBatchLink({
12
14
  url: `${process.env.EXPO_PUBLIC_SERVER_URL}/trpc`,
15
+ {{#if auth}}
13
16
  headers() {
14
17
  const headers = new Map<string, string>();
15
18
  const cookies = authClient.getCookie();
@@ -18,6 +21,7 @@ const trpcClient = createTRPCClient<AppRouter>({
18
21
  }
19
22
  return Object.fromEntries(headers);
20
23
  },
24
+ {{/if}}
21
25
  }),
22
26
  ],
23
27
  });
@@ -3,7 +3,7 @@
3
3
  import { QueryCache, QueryClient } from '@tanstack/react-query';
4
4
  import { createTRPCClient, httpBatchLink } from '@trpc/client';
5
5
  import { createTRPCOptionsProxy } from '@trpc/tanstack-react-query';
6
- import type { AppRouter } from '../../../server/src/routers'; {{! Adjust path if necessary }}
6
+ import type { AppRouter } from '../../../server/src/routers';
7
7
  import { toast } from 'sonner';
8
8
 
9
9
  export const queryClient = new QueryClient({
@@ -5,11 +5,22 @@ import { ScrollView, Text, TouchableOpacity, View } from "react-native";
5
5
  import { Container } from "@/components/container";
6
6
  import { SignIn } from "@/components/sign-in";
7
7
  import { SignUp } from "@/components/sign-up";
8
+ {{#if (eq api "orpc")}}
9
+ import { queryClient, orpc } from "@/utils/orpc";
10
+ {{/if}}
11
+ {{#if (eq api "trpc")}}
8
12
  import { queryClient, trpc } from "@/utils/trpc";
13
+ {{/if}}
9
14
 
10
15
  export default function Home() {
16
+ {{#if (eq api "orpc")}}
17
+ const healthCheck = useQuery(orpc.healthCheck.queryOptions());
18
+ const privateData = useQuery(orpc.privateData.queryOptions());
19
+ {{/if}}
20
+ {{#if (eq api "trpc")}}
11
21
  const healthCheck = useQuery(trpc.healthCheck.queryOptions());
12
22
  const privateData = useQuery(trpc.privateData.queryOptions());
23
+ {{/if}}
13
24
  const { data: session } = authClient.useSession();
14
25
 
15
26
  return (
@@ -1,5 +1,10 @@
1
1
  import { authClient } from "@/lib/auth-client";
2
+ {{#if (eq api "trpc")}}
2
3
  import { queryClient } from "@/utils/trpc";
4
+ {{/if}}
5
+ {{#if (eq api "orpc")}}
6
+ import { queryClient } from "@/utils/orpc";
7
+ {{/if}}
3
8
  import { useState } from "react";
4
9
  import {
5
10
  ActivityIndicator,
@@ -85,4 +90,4 @@ export function SignIn() {
85
90
  </TouchableOpacity>
86
91
  </View>
87
92
  );
88
- }
93
+ }
@@ -1,5 +1,10 @@
1
1
  import { authClient } from "@/lib/auth-client";
2
+ {{#if (eq api "trpc")}}
2
3
  import { queryClient } from "@/utils/trpc";
4
+ {{/if}}
5
+ {{#if (eq api "orpc")}}
6
+ import { queryClient } from "@/utils/orpc";
7
+ {{/if}}
3
8
  import { useState } from "react";
4
9
  import {
5
10
  ActivityIndicator,
@@ -1,6 +1,9 @@
1
1
  {{#if (eq orm "prisma")}}
2
2
  import { betterAuth } from "better-auth";
3
3
  import { prismaAdapter } from "better-auth/adapters/prisma";
4
+ {{#if (includes frontend "native")}}
5
+ import { expo } from "@better-auth/expo";
6
+ {{/if}}
4
7
  import prisma from "../../prisma";
5
8
 
6
9
  export const auth = betterAuth({
@@ -9,14 +12,27 @@ export const auth = betterAuth({
9
12
  {{#if (eq database "sqlite")}}provider: "sqlite"{{/if}}
10
13
  {{#if (eq database "mysql")}}provider: "mysql"{{/if}}
11
14
  }),
12
- trustedOrigins: [process.env.CORS_ORIGIN || ""],
13
- emailAndPassword: { enabled: true }
15
+ trustedOrigins: [
16
+ process.env.CORS_ORIGIN || "",{{#if (includes frontend "native")}}
17
+ "my-better-t-app://",{{/if}}
18
+ ],
19
+ emailAndPassword: {
20
+ enabled: true,
21
+ }
22
+
23
+ {{~#if (includes frontend "native")}}
24
+ ,
25
+ plugins: [expo()]
26
+ {{/if~}}
14
27
  });
15
28
  {{/if}}
16
29
 
17
30
  {{#if (eq orm "drizzle")}}
18
31
  import { betterAuth } from "better-auth";
19
32
  import { drizzleAdapter } from "better-auth/adapters/drizzle";
33
+ {{#if (includes frontend "native")}}
34
+ import { expo } from "@better-auth/expo";
35
+ {{/if}}
20
36
  import { db } from "../db";
21
37
  import * as schema from "../db/schema/auth";
22
38
 
@@ -25,19 +41,42 @@ export const auth = betterAuth({
25
41
  {{#if (eq database "postgres")}}provider: "pg",{{/if}}
26
42
  {{#if (eq database "sqlite")}}provider: "sqlite",{{/if}}
27
43
  {{#if (eq database "mysql")}}provider: "mysql",{{/if}}
28
- schema: schema
44
+ schema: schema,
29
45
  }),
30
- trustedOrigins: [process.env.CORS_ORIGIN || ""],
31
- emailAndPassword: { enabled: true }
46
+ trustedOrigins: [
47
+ process.env.CORS_ORIGIN || "",{{#if (includes frontend "native")}}
48
+ "my-better-t-app://",{{/if}}
49
+ ],
50
+ emailAndPassword: {
51
+ enabled: true,
52
+ }
53
+
54
+ {{~#if (includes frontend "native")}}
55
+ ,
56
+ plugins: [expo()]
57
+ {{/if~}}
32
58
  });
33
59
  {{/if}}
34
60
 
35
61
  {{#if (eq orm "none")}}
36
62
  import { betterAuth } from "better-auth";
63
+ {{#if (includes frontend "native")}}
64
+ import { expo } from "@better-auth/expo";
65
+ {{/if}}
37
66
 
38
67
  export const auth = betterAuth({
39
- database: "",
40
- trustedOrigins: [process.env.CORS_ORIGIN || ""],
41
- emailAndPassword: { enabled: true }
68
+ database: "", // Invalid configuration
69
+ trustedOrigins: [
70
+ process.env.CORS_ORIGIN || "",{{#if (includes frontend "native")}}
71
+ "my-better-t-app://", // Use hardcoded scheme{{/if}}
72
+ ],
73
+ emailAndPassword: {
74
+ enabled: true,
75
+ }
76
+
77
+ {{~#if (includes frontend "native")}}
78
+ ,
79
+ plugins: [expo()]
80
+ {{/if~}}
42
81
  });
43
- {{/if}}
82
+ {{/if}}
@@ -0,0 +1,78 @@
1
+ <script setup lang="ts">
2
+ import { z } from 'zod'
3
+ // import { authClient } from "~/lib/auth-client";
4
+ const {$authClient} = useNuxtApp()
5
+ import type { FormSubmitEvent } from '#ui/types'
6
+
7
+ const emit = defineEmits(['switchToSignUp'])
8
+
9
+ const toast = useToast()
10
+ const loading = ref(false)
11
+
12
+ const schema = z.object({
13
+ email: z.string().email('Invalid email address'),
14
+ password: z.string().min(6, 'Password must be at least 6 characters'),
15
+ })
16
+
17
+ type Schema = z.output<typeof schema>
18
+
19
+ const state = reactive({
20
+ email: '',
21
+ password: '',
22
+ })
23
+
24
+ async function onSubmit (event: FormSubmitEvent<Schema>) {
25
+ loading.value = true
26
+ try {
27
+ await $authClient.signIn.email(
28
+ {
29
+ email: event.data.email,
30
+ password: event.data.password,
31
+ },
32
+ {
33
+ onSuccess: () => {
34
+ toast.add({ title: 'Sign in successful' })
35
+ navigateTo('/dashboard', { replace: true })
36
+ },
37
+ onError: (error) => {
38
+ toast.add({ title: 'Sign in failed', description: error.error.message })
39
+ },
40
+ },
41
+ )
42
+ } catch (error: any) {
43
+ toast.add({ title: 'An unexpected error occurred', description: error.message || 'Please try again.' })
44
+ } finally {
45
+ loading.value = false
46
+ }
47
+ }
48
+ </script>
49
+
50
+ <template>
51
+ <div class="mx-auto w-full mt-10 max-w-md p-6">
52
+ <h1 class="mb-6 text-center text-3xl font-bold">Welcome Back</h1>
53
+
54
+ <UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
55
+ <UFormField label="Email" name="email">
56
+ <UInput v-model="state.email" type="email" class="w-full" />
57
+ </UFormField>
58
+
59
+ <UFormField label="Password" name="password">
60
+ <UInput v-model="state.password" type="password" class="w-full" />
61
+ </UFormField>
62
+
63
+ <UButton type="submit" block :loading="loading">
64
+ Sign In
65
+ </UButton>
66
+ </UForm>
67
+
68
+ <div class="mt-4 text-center">
69
+ <UButton
70
+ variant="link"
71
+ @click="$emit('switchToSignUp')"
72
+ class="text-primary hover:text-primary-dark"
73
+ >
74
+ Need an account? Sign Up
75
+ </UButton>
76
+ </div>
77
+ </div>
78
+ </template>
@@ -0,0 +1,85 @@
1
+ <script setup lang="ts">
2
+ import { z } from 'zod'
3
+ import type { FormSubmitEvent } from '#ui/types'
4
+ // import { authClient } from "~/lib/auth-client";
5
+ const {$authClient} = useNuxtApp()
6
+
7
+ const emit = defineEmits(['switchToSignIn'])
8
+
9
+ const toast = useToast()
10
+ const loading = ref(false)
11
+
12
+ const schema = z.object({
13
+ name: z.string().min(2, 'Name must be at least 2 characters'),
14
+ email: z.string().email('Invalid email address'),
15
+ password: z.string().min(6, 'Password must be at least 6 characters'),
16
+ })
17
+
18
+ type Schema = z.output<typeof schema>
19
+
20
+ const state = reactive({
21
+ name: '',
22
+ email: '',
23
+ password: '',
24
+ })
25
+
26
+ async function onSubmit (event: FormSubmitEvent<Schema>) {
27
+ loading.value = true
28
+ try {
29
+ await $authClient.signUp.email(
30
+ {
31
+ name: event.data.name,
32
+ email: event.data.email,
33
+ password: event.data.password,
34
+ },
35
+ {
36
+ onSuccess: () => {
37
+ toast.add({ title: 'Sign up successful' })
38
+ navigateTo('/dashboard', { replace: true })
39
+ },
40
+ onError: (error) => {
41
+ toast.add({ title: 'Sign up failed', description: error.error.message })
42
+ },
43
+ },
44
+ )
45
+ } catch (error: any) {
46
+ toast.add({ title: 'An unexpected error occurred', description: error.message || 'Please try again.' })
47
+ } finally {
48
+ loading.value = false
49
+ }
50
+ }
51
+ </script>
52
+
53
+ <template>
54
+ <div class="mx-auto w-full mt-10 max-w-md p-6">
55
+ <h1 class="mb-6 text-center text-3xl font-bold">Create Account</h1>
56
+
57
+ <UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
58
+ <UFormField label="Name" name="name">
59
+ <UInput v-model="state.name" class="w-full" />
60
+ </UFormField>
61
+
62
+ <UFormField label="Email" name="email">
63
+ <UInput v-model="state.email" type="email" class="w-full" />
64
+ </UFormField>
65
+
66
+ <UFormField label="Password" name="password">
67
+ <UInput v-model="state.password" type="password" class="w-full" />
68
+ </UFormField>
69
+
70
+ <UButton type="submit" block :loading="loading">
71
+ Sign Up
72
+ </UButton>
73
+ </UForm>
74
+
75
+ <div class="mt-4 text-center">
76
+ <UButton
77
+ variant="link"
78
+ @click="$emit('switchToSignIn')"
79
+ class="text-primary hover:text-primary-dark"
80
+ >
81
+ Already have an account? Sign In
82
+ </UButton>
83
+ </div>
84
+ </div>
85
+ </template>
@@ -0,0 +1,43 @@
1
+ <script setup lang="ts">
2
+
3
+ // import { authClient } from "~/lib/auth-client";
4
+ const {$authClient} = useNuxtApp()
5
+ const session = $authClient.useSession()
6
+ const toast = useToast()
7
+
8
+ const handleSignOut = async () => {
9
+ try {
10
+ await $authClient.signOut({
11
+ fetchOptions: {
12
+ onSuccess: async () => {
13
+ toast.add({ title: 'Signed out successfully' })
14
+ await navigateTo('/', { replace: true, external: true })
15
+ },
16
+ onError: (error) => {
17
+ toast.add({ title: 'Sign out failed', description: error?.error?.message || 'Unknown error'})
18
+ }
19
+ },
20
+ })
21
+ } catch (error: any) {
22
+ toast.add({ title: 'An unexpected error occurred during sign out', description: error.message || 'Please try again.'})
23
+ }
24
+ }
25
+ </script>
26
+
27
+ <template>
28
+ <div>
29
+ <USkeleton v-if="session.isPending" class="h-9 w-24" />
30
+
31
+ <UButton v-else-if="!session.data" variant="outline" to="/login">
32
+ Sign In
33
+ </UButton>
34
+
35
+ <UButton
36
+ v-else
37
+ variant="solid"
38
+ icon="i-lucide-log-out"
39
+ label="Sign out"
40
+ @click="handleSignOut()"
41
+ />
42
+ </div>
43
+ </template>
@@ -0,0 +1,12 @@
1
+ export default defineNuxtRouteMiddleware(async (to, from) => {
2
+ if (import.meta.server) return
3
+
4
+ const { $authClient } = useNuxtApp()
5
+ const session = $authClient.useSession()
6
+
7
+ if (session.value.isPending || !session.value) {
8
+ if (to.path === "/dashboard") {
9
+ return navigateTo("/login");
10
+ }
11
+ }
12
+ });
@@ -0,0 +1,27 @@
1
+ <script setup lang="ts">
2
+ import { useQuery } from '@tanstack/vue-query'
3
+ const {$authClient} = useNuxtApp()
4
+
5
+ definePageMeta({
6
+ middleware: ['auth']
7
+ })
8
+
9
+ const { $orpc } = useNuxtApp()
10
+
11
+ const session = $authClient.useSession()
12
+
13
+ const privateData = useQuery($orpc.privateData.queryOptions())
14
+
15
+ </script>
16
+
17
+ <template>
18
+ <div class="container mx-auto p-4">
19
+ <h1 class="text-2xl font-bold mb-4">Dashboard</h1>
20
+ <div v-if="session?.data?.user">
21
+ <p class="mb-2">Welcome {{ session.data.user.name }}</p>
22
+ </div>
23
+ <div v-if="privateData.status.value === 'pending'">Loading private data...</div>
24
+ <div v-else-if="privateData.status.value === 'error'">Error loading private data: {{ privateData.error.value?.message }}</div>
25
+ <p v-else-if="privateData.data.value">Private Data: {{ privateData.data.value.message }}</p>
26
+ </div>
27
+ </template>
@@ -0,0 +1,24 @@
1
+ <script setup lang="ts">
2
+ const { $authClient } = useNuxtApp();
3
+ import SignInForm from "~/components/SignInForm.vue";
4
+ import SignUpForm from "~/components/SignUpForm.vue";
5
+
6
+ const session = $authClient.useSession();
7
+ const showSignIn = ref(true);
8
+
9
+ watchEffect(() => {
10
+ if (!session?.value.isPending && session?.value.data) {
11
+ navigateTo("/dashboard", { replace: true });
12
+ }
13
+ });
14
+ </script>
15
+
16
+ <template>
17
+ <div>
18
+ <Loader v-if="session.isPending" />
19
+ <div v-else-if="!session.data">
20
+ <SignInForm v-if="showSignIn" @switch-to-sign-up="showSignIn = false" />
21
+ <SignUpForm v-else @switch-to-sign-in="showSignIn = true" />
22
+ </div>
23
+ </div>
24
+ </template>
@@ -0,0 +1,16 @@
1
+ import { createAuthClient } from "better-auth/vue";
2
+
3
+ export default defineNuxtPlugin(nuxtApp => {
4
+ const config = useRuntimeConfig()
5
+ const serverUrl = config.public.serverURL
6
+
7
+ const authClient = createAuthClient({
8
+ baseURL: serverUrl
9
+ })
10
+
11
+ return {
12
+ provide: {
13
+ authClient: authClient
14
+ }
15
+ }
16
+ })
@@ -39,6 +39,8 @@ app.use(
39
39
  app.all("/api/auth{/*path}", toNodeHandler(auth));
40
40
  {{/if}}
41
41
 
42
+ app.use(express.json())
43
+
42
44
  {{#if (eq api "trpc")}}
43
45
  app.use(
44
46
  "/trpc",
@@ -0,0 +1,64 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch, nextTick } from 'vue'
3
+ import { useChat } from '@ai-sdk/vue'
4
+
5
+ const config = useRuntimeConfig()
6
+ const serverUrl = config.public.serverURL
7
+
8
+ const { messages, input, handleSubmit } = useChat({
9
+ api: `${serverUrl}/ai`,
10
+ })
11
+
12
+ const messagesEndRef = ref<null | HTMLDivElement>(null)
13
+
14
+ watch(messages, async () => {
15
+ await nextTick()
16
+ messagesEndRef.value?.scrollIntoView({ behavior: 'smooth' })
17
+ })
18
+
19
+ // Helper: Concatenate all text parts for a message
20
+ function getMessageText(message: any) {
21
+ return message.parts
22
+ .filter((part: any) => part.type === 'text')
23
+ .map((part: any) => part.text)
24
+ .join('')
25
+ }
26
+ </script>
27
+
28
+ <template>
29
+ <div class="grid grid-rows-[1fr_auto] overflow-hidden w-full mx-auto p-4">
30
+ <div class="overflow-y-auto space-y-4 pb-4">
31
+ <div v-if="messages.length === 0" class="text-center text-muted-foreground mt-8">
32
+ Ask me anything to get started!
33
+ </div>
34
+ <div
35
+ v-for="message in messages"
36
+ :key="message.id"
37
+ :class="[
38
+ 'p-3 rounded-lg',
39
+ message.role === 'user' ? 'bg-primary/10 ml-8' : 'bg-secondary/20 mr-8'
40
+ ]"
41
+ >
42
+ <p class="text-sm font-semibold mb-1">
43
+ {{ message.role === 'user' ? 'You' : 'AI Assistant' }}
44
+ </p>
45
+ <div class="whitespace-pre-wrap">{{ getMessageText(message) }}</div>
46
+ </div>
47
+ <div ref="messagesEndRef" />
48
+ </div>
49
+
50
+ <form @submit.prevent="handleSubmit" class="w-full flex items-center space-x-2 pt-2 border-t">
51
+ <UInput
52
+ name="prompt"
53
+ v-model="input"
54
+ placeholder="Type your message..."
55
+ class="flex-1"
56
+ autocomplete="off"
57
+ autofocus
58
+ />
59
+ <UButton type="submit" color="primary" size="md" square>
60
+ <UIcon name="i-lucide-send" class="w-5 h-5" />
61
+ </UButton>
62
+ </form>
63
+ </div>
64
+ </template>