mirror of
https://github.com/FranP-code/create-better-t-stack.git
synced 2025-10-12 23:52:15 +00:00
add nuxt and expo with orpc
This commit is contained in:
@@ -5,11 +5,22 @@ import { ScrollView, Text, TouchableOpacity, View } from "react-native";
|
||||
import { Container } from "@/components/container";
|
||||
import { SignIn } from "@/components/sign-in";
|
||||
import { SignUp } from "@/components/sign-up";
|
||||
{{#if (eq api "orpc")}}
|
||||
import { queryClient, orpc } from "@/utils/orpc";
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
import { queryClient, trpc } from "@/utils/trpc";
|
||||
{{/if}}
|
||||
|
||||
export default function Home() {
|
||||
{{#if (eq api "orpc")}}
|
||||
const healthCheck = useQuery(orpc.healthCheck.queryOptions());
|
||||
const privateData = useQuery(orpc.privateData.queryOptions());
|
||||
{{/if}}
|
||||
{{#if (eq api "trpc")}}
|
||||
const healthCheck = useQuery(trpc.healthCheck.queryOptions());
|
||||
const privateData = useQuery(trpc.privateData.queryOptions());
|
||||
{{/if}}
|
||||
const { data: session } = authClient.useSession();
|
||||
|
||||
return (
|
||||
@@ -1,5 +1,10 @@
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
{{#if (eq api "trpc")}}
|
||||
import { queryClient } from "@/utils/trpc";
|
||||
{{/if}}
|
||||
{{#if (eq api "orpc")}}
|
||||
import { queryClient } from "@/utils/orpc";
|
||||
{{/if}}
|
||||
import { useState } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
@@ -85,4 +90,4 @@ export function SignIn() {
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,10 @@
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
{{#if (eq api "trpc")}}
|
||||
import { queryClient } from "@/utils/trpc";
|
||||
{{/if}}
|
||||
{{#if (eq api "orpc")}}
|
||||
import { queryClient } from "@/utils/orpc";
|
||||
{{/if}}
|
||||
import { useState } from "react";
|
||||
import {
|
||||
ActivityIndicator,
|
||||
@@ -1,28 +0,0 @@
|
||||
import { authClient } from "@/lib/auth-client";
|
||||
import { QueryClient } from "@tanstack/react-query";
|
||||
import { createTRPCClient, httpBatchLink } from "@trpc/client";
|
||||
import { createTRPCOptionsProxy } from "@trpc/tanstack-react-query";
|
||||
import type { AppRouter } from "../../server/src/routers";
|
||||
|
||||
export const queryClient = new QueryClient();
|
||||
|
||||
const trpcClient = createTRPCClient<AppRouter>({
|
||||
links: [
|
||||
httpBatchLink({
|
||||
url: `${process.env.EXPO_PUBLIC_SERVER_URL}/trpc`,
|
||||
headers() {
|
||||
const headers = new Map<string, string>();
|
||||
const cookies = authClient.getCookie();
|
||||
if (cookies) {
|
||||
headers.set("Cookie", cookies);
|
||||
}
|
||||
return Object.fromEntries(headers);
|
||||
},
|
||||
}),
|
||||
],
|
||||
});
|
||||
|
||||
export const trpc = createTRPCOptionsProxy<AppRouter>({
|
||||
client: trpcClient,
|
||||
queryClient,
|
||||
});
|
||||
@@ -1,6 +1,9 @@
|
||||
{{#if (eq orm "prisma")}}
|
||||
import { betterAuth } from "better-auth";
|
||||
import { prismaAdapter } from "better-auth/adapters/prisma";
|
||||
{{#if (includes frontend "native")}}
|
||||
import { expo } from "@better-auth/expo";
|
||||
{{/if}}
|
||||
import prisma from "../../prisma";
|
||||
|
||||
export const auth = betterAuth({
|
||||
@@ -9,14 +12,27 @@ export const auth = betterAuth({
|
||||
{{#if (eq database "sqlite")}}provider: "sqlite"{{/if}}
|
||||
{{#if (eq database "mysql")}}provider: "mysql"{{/if}}
|
||||
}),
|
||||
trustedOrigins: [process.env.CORS_ORIGIN || ""],
|
||||
emailAndPassword: { enabled: true }
|
||||
trustedOrigins: [
|
||||
process.env.CORS_ORIGIN || "",{{#if (includes frontend "native")}}
|
||||
"my-better-t-app://",{{/if}}
|
||||
],
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
}
|
||||
|
||||
{{~#if (includes frontend "native")}}
|
||||
,
|
||||
plugins: [expo()]
|
||||
{{/if~}}
|
||||
});
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq orm "drizzle")}}
|
||||
import { betterAuth } from "better-auth";
|
||||
import { drizzleAdapter } from "better-auth/adapters/drizzle";
|
||||
{{#if (includes frontend "native")}}
|
||||
import { expo } from "@better-auth/expo";
|
||||
{{/if}}
|
||||
import { db } from "../db";
|
||||
import * as schema from "../db/schema/auth";
|
||||
|
||||
@@ -25,19 +41,42 @@ export const auth = betterAuth({
|
||||
{{#if (eq database "postgres")}}provider: "pg",{{/if}}
|
||||
{{#if (eq database "sqlite")}}provider: "sqlite",{{/if}}
|
||||
{{#if (eq database "mysql")}}provider: "mysql",{{/if}}
|
||||
schema: schema
|
||||
schema: schema,
|
||||
}),
|
||||
trustedOrigins: [process.env.CORS_ORIGIN || ""],
|
||||
emailAndPassword: { enabled: true }
|
||||
trustedOrigins: [
|
||||
process.env.CORS_ORIGIN || "",{{#if (includes frontend "native")}}
|
||||
"my-better-t-app://",{{/if}}
|
||||
],
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
}
|
||||
|
||||
{{~#if (includes frontend "native")}}
|
||||
,
|
||||
plugins: [expo()]
|
||||
{{/if~}}
|
||||
});
|
||||
{{/if}}
|
||||
|
||||
{{#if (eq orm "none")}}
|
||||
import { betterAuth } from "better-auth";
|
||||
{{#if (includes frontend "native")}}
|
||||
import { expo } from "@better-auth/expo";
|
||||
{{/if}}
|
||||
|
||||
export const auth = betterAuth({
|
||||
database: "",
|
||||
trustedOrigins: [process.env.CORS_ORIGIN || ""],
|
||||
emailAndPassword: { enabled: true }
|
||||
database: "", // Invalid configuration
|
||||
trustedOrigins: [
|
||||
process.env.CORS_ORIGIN || "",{{#if (includes frontend "native")}}
|
||||
"my-better-t-app://", // Use hardcoded scheme{{/if}}
|
||||
],
|
||||
emailAndPassword: {
|
||||
enabled: true,
|
||||
}
|
||||
|
||||
{{~#if (includes frontend "native")}}
|
||||
,
|
||||
plugins: [expo()]
|
||||
{{/if~}}
|
||||
});
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
@@ -0,0 +1,78 @@
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
// import { authClient } from "~/lib/auth-client";
|
||||
const {$authClient} = useNuxtApp()
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
|
||||
const emit = defineEmits(['switchToSignUp'])
|
||||
|
||||
const toast = useToast()
|
||||
const loading = ref(false)
|
||||
|
||||
const schema = z.object({
|
||||
email: z.string().email('Invalid email address'),
|
||||
password: z.string().min(6, 'Password must be at least 6 characters'),
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const state = reactive({
|
||||
email: '',
|
||||
password: '',
|
||||
})
|
||||
|
||||
async function onSubmit (event: FormSubmitEvent<Schema>) {
|
||||
loading.value = true
|
||||
try {
|
||||
await $authClient.signIn.email(
|
||||
{
|
||||
email: event.data.email,
|
||||
password: event.data.password,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.add({ title: 'Sign in successful' })
|
||||
navigateTo('/dashboard', { replace: true })
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.add({ title: 'Sign in failed', description: error.error.message })
|
||||
},
|
||||
},
|
||||
)
|
||||
} catch (error: any) {
|
||||
toast.add({ title: 'An unexpected error occurred', description: error.message || 'Please try again.' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-auto w-full mt-10 max-w-md p-6">
|
||||
<h1 class="mb-6 text-center text-3xl font-bold">Welcome Back</h1>
|
||||
|
||||
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
|
||||
<UFormField label="Email" name="email">
|
||||
<UInput v-model="state.email" type="email" class="w-full" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" class="w-full" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit" block :loading="loading">
|
||||
Sign In
|
||||
</UButton>
|
||||
</UForm>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<UButton
|
||||
variant="link"
|
||||
@click="$emit('switchToSignUp')"
|
||||
class="text-primary hover:text-primary-dark"
|
||||
>
|
||||
Need an account? Sign Up
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,85 @@
|
||||
<script setup lang="ts">
|
||||
import { z } from 'zod'
|
||||
import type { FormSubmitEvent } from '#ui/types'
|
||||
// import { authClient } from "~/lib/auth-client";
|
||||
const {$authClient} = useNuxtApp()
|
||||
|
||||
const emit = defineEmits(['switchToSignIn'])
|
||||
|
||||
const toast = useToast()
|
||||
const loading = ref(false)
|
||||
|
||||
const schema = z.object({
|
||||
name: z.string().min(2, 'Name must be at least 2 characters'),
|
||||
email: z.string().email('Invalid email address'),
|
||||
password: z.string().min(6, 'Password must be at least 6 characters'),
|
||||
})
|
||||
|
||||
type Schema = z.output<typeof schema>
|
||||
|
||||
const state = reactive({
|
||||
name: '',
|
||||
email: '',
|
||||
password: '',
|
||||
})
|
||||
|
||||
async function onSubmit (event: FormSubmitEvent<Schema>) {
|
||||
loading.value = true
|
||||
try {
|
||||
await $authClient.signUp.email(
|
||||
{
|
||||
name: event.data.name,
|
||||
email: event.data.email,
|
||||
password: event.data.password,
|
||||
},
|
||||
{
|
||||
onSuccess: () => {
|
||||
toast.add({ title: 'Sign up successful' })
|
||||
navigateTo('/dashboard', { replace: true })
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.add({ title: 'Sign up failed', description: error.error.message })
|
||||
},
|
||||
},
|
||||
)
|
||||
} catch (error: any) {
|
||||
toast.add({ title: 'An unexpected error occurred', description: error.message || 'Please try again.' })
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="mx-auto w-full mt-10 max-w-md p-6">
|
||||
<h1 class="mb-6 text-center text-3xl font-bold">Create Account</h1>
|
||||
|
||||
<UForm :schema="schema" :state="state" class="space-y-4" @submit="onSubmit">
|
||||
<UFormField label="Name" name="name">
|
||||
<UInput v-model="state.name" class="w-full" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Email" name="email">
|
||||
<UInput v-model="state.email" type="email" class="w-full" />
|
||||
</UFormField>
|
||||
|
||||
<UFormField label="Password" name="password">
|
||||
<UInput v-model="state.password" type="password" class="w-full" />
|
||||
</UFormField>
|
||||
|
||||
<UButton type="submit" block :loading="loading">
|
||||
Sign Up
|
||||
</UButton>
|
||||
</UForm>
|
||||
|
||||
<div class="mt-4 text-center">
|
||||
<UButton
|
||||
variant="link"
|
||||
@click="$emit('switchToSignIn')"
|
||||
class="text-primary hover:text-primary-dark"
|
||||
>
|
||||
Already have an account? Sign In
|
||||
</UButton>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
43
apps/cli/templates/auth/web/nuxt/app/components/UserMenu.vue
Normal file
43
apps/cli/templates/auth/web/nuxt/app/components/UserMenu.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<script setup lang="ts">
|
||||
|
||||
// import { authClient } from "~/lib/auth-client";
|
||||
const {$authClient} = useNuxtApp()
|
||||
const session = $authClient.useSession()
|
||||
const toast = useToast()
|
||||
|
||||
const handleSignOut = async () => {
|
||||
try {
|
||||
await $authClient.signOut({
|
||||
fetchOptions: {
|
||||
onSuccess: async () => {
|
||||
toast.add({ title: 'Signed out successfully' })
|
||||
await navigateTo('/', { replace: true, external: true })
|
||||
},
|
||||
onError: (error) => {
|
||||
toast.add({ title: 'Sign out failed', description: error?.error?.message || 'Unknown error'})
|
||||
}
|
||||
},
|
||||
})
|
||||
} catch (error: any) {
|
||||
toast.add({ title: 'An unexpected error occurred during sign out', description: error.message || 'Please try again.'})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<USkeleton v-if="session.isPending" class="h-9 w-24" />
|
||||
|
||||
<UButton v-else-if="!session.data" variant="outline" to="/login">
|
||||
Sign In
|
||||
</UButton>
|
||||
|
||||
<UButton
|
||||
v-else
|
||||
variant="solid"
|
||||
icon="i-lucide-log-out"
|
||||
label="Sign out"
|
||||
@click="handleSignOut()"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
12
apps/cli/templates/auth/web/nuxt/app/middleware/auth.ts
Normal file
12
apps/cli/templates/auth/web/nuxt/app/middleware/auth.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
export default defineNuxtRouteMiddleware(async (to, from) => {
|
||||
if (import.meta.server) return
|
||||
|
||||
const { $authClient } = useNuxtApp()
|
||||
const session = $authClient.useSession()
|
||||
|
||||
if (session.value.isPending || !session.value) {
|
||||
if (to.path === "/dashboard") {
|
||||
return navigateTo("/login");
|
||||
}
|
||||
}
|
||||
});
|
||||
27
apps/cli/templates/auth/web/nuxt/app/pages/dashboard.vue
Normal file
27
apps/cli/templates/auth/web/nuxt/app/pages/dashboard.vue
Normal file
@@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { useQuery } from '@tanstack/vue-query'
|
||||
const {$authClient} = useNuxtApp()
|
||||
|
||||
definePageMeta({
|
||||
middleware: ['auth']
|
||||
})
|
||||
|
||||
const { $orpc } = useNuxtApp()
|
||||
|
||||
const session = $authClient.useSession()
|
||||
|
||||
const privateData = useQuery($orpc.privateData.queryOptions())
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="container mx-auto p-4">
|
||||
<h1 class="text-2xl font-bold mb-4">Dashboard</h1>
|
||||
<div v-if="session?.data?.user">
|
||||
<p class="mb-2">Welcome {{ session.data.user.name }}</p>
|
||||
</div>
|
||||
<div v-if="privateData.status.value === 'pending'">Loading private data...</div>
|
||||
<div v-else-if="privateData.status.value === 'error'">Error loading private data: {{ privateData.error.value?.message }}</div>
|
||||
<p v-else-if="privateData.data.value">Private Data: {{ privateData.data.value.message }}</p>
|
||||
</div>
|
||||
</template>
|
||||
24
apps/cli/templates/auth/web/nuxt/app/pages/login.vue
Normal file
24
apps/cli/templates/auth/web/nuxt/app/pages/login.vue
Normal file
@@ -0,0 +1,24 @@
|
||||
<script setup lang="ts">
|
||||
const { $authClient } = useNuxtApp();
|
||||
import SignInForm from "~/components/SignInForm.vue";
|
||||
import SignUpForm from "~/components/SignUpForm.vue";
|
||||
|
||||
const session = $authClient.useSession();
|
||||
const showSignIn = ref(true);
|
||||
|
||||
watchEffect(() => {
|
||||
if (!session?.value.isPending && session?.value.data) {
|
||||
navigateTo("/dashboard", { replace: true });
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<Loader v-if="session.isPending" />
|
||||
<div v-else-if="!session.data">
|
||||
<SignInForm v-if="showSignIn" @switch-to-sign-up="showSignIn = false" />
|
||||
<SignUpForm v-else @switch-to-sign-in="showSignIn = true" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
16
apps/cli/templates/auth/web/nuxt/app/plugins/auth-client.ts
Normal file
16
apps/cli/templates/auth/web/nuxt/app/plugins/auth-client.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { createAuthClient } from "better-auth/vue";
|
||||
|
||||
export default defineNuxtPlugin(nuxtApp => {
|
||||
const config = useRuntimeConfig()
|
||||
const serverUrl = config.public.serverURL
|
||||
|
||||
const authClient = createAuthClient({
|
||||
baseURL: serverUrl
|
||||
})
|
||||
|
||||
return {
|
||||
provide: {
|
||||
authClient: authClient
|
||||
}
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user