add nuxt and expo with orpc

This commit is contained in:
Aman Varshney
2025-04-23 13:03:38 +05:30
parent 49c7d4f436
commit d3a80b7e63
145 changed files with 2013 additions and 874 deletions

View File

@@ -1,10 +1,20 @@
import { useQuery } from "@tanstack/react-query";
import { View, Text, ScrollView } from "react-native";
import { Container } from "@/components/container";
{{#if (eq api "orpc")}}
import { orpc } from "@/utils/orpc";
{{/if}}
{{#if (eq api "trpc")}}
import { trpc } from "@/utils/trpc";
{{/if}}
export default function Home() {
{{#if (eq api "orpc")}}
const healthCheck = useQuery(orpc.healthCheck.queryOptions());
{{/if}}
{{#if (eq api "trpc")}}
const healthCheck = useQuery(trpc.healthCheck.queryOptions());
{{/if}}
return (
<Container>

View File

@@ -9,7 +9,12 @@ import {
import { StatusBar } from "expo-status-bar";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import "../global.css";
{{#if (eq api "trpc")}}
import { queryClient } from "@/utils/trpc";
{{/if}}
{{#if (eq api "orpc")}}
import { queryClient } from "@/utils/orpc";
{{/if}}
import { NAV_THEME } from "@/lib/constants";
import React, { useRef } from "react";
import { useColorScheme } from "@/lib/use-color-scheme";
@@ -56,12 +61,12 @@ export default function RootLayout() {
<QueryClientProvider client={queryClient}>
<ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
<StatusBar style={isDarkColorScheme ? "light" : "dark"} />
<GestureHandlerRootView style={{ flex: 1 }}>
<GestureHandlerRootView style=\{{ flex: 1 }}>
<Stack>
<Stack.Screen name="(drawer)" options={{ headerShown: false }} />
<Stack.Screen name="(drawer)" options=\{{ headerShown: false }} />
<Stack.Screen
name="modal"
options={{ title: "Modal", presentation: "modal" }}
options=\{{ title: "Modal", presentation: "modal" }}
/>
</Stack>
</GestureHandlerRootView>

View File

@@ -16,9 +16,6 @@
"@react-navigation/native": "^7.0.14",
"@tanstack/react-form": "^1.0.5",
"@tanstack/react-query": "^5.69.2",
"@trpc/client": "^11.0.0",
"@trpc/server": "^11.0.0",
"@trpc/tanstack-react-query": "^11.0.0",
"expo": "^52.0.44",
"expo-constants": "~17.0.8",
"expo-linking": "~7.0.5",

View File

@@ -1,19 +0,0 @@
import type { AppRouter } from "../../server/src/routers";
import { QueryClient } from "@tanstack/react-query";
import { createTRPCClient, httpBatchLink } from "@trpc/client";
import { createTRPCOptionsProxy } from "@trpc/tanstack-react-query";
export const queryClient = new QueryClient();
const trpcClient = createTRPCClient<AppRouter>({
links: [
httpBatchLink({
url: `${process.env.EXPO_PUBLIC_SERVER_URL}/trpc`,
}),
],
});
export const trpc = createTRPCOptionsProxy<AppRouter>({
client: trpcClient,
queryClient,
});

View File

@@ -0,0 +1,24 @@
# Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
# Node dependencies
node_modules
# Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example

View File

@@ -0,0 +1,15 @@
export default defineAppConfig({
// https://ui.nuxt.com/getting-started/theme#design-system
ui: {
colors: {
primary: 'emerald',
neutral: 'slate',
},
button: {
defaultVariants: {
// Set default button color to neutral
// color: 'neutral'
}
}
}
})

View File

@@ -0,0 +1,13 @@
<script setup lang="ts">
import { VueQueryDevtools } from '@tanstack/vue-query-devtools'
</script>
<template>
<NuxtLoadingIndicator />
<UApp>
<NuxtLayout>
<NuxtPage />
</NuxtLayout>
</UApp>
<VueQueryDevtools />
</template>

View File

@@ -0,0 +1,2 @@
@import "tailwindcss";
@import "@nuxt/ui";

View File

@@ -0,0 +1,45 @@
<script setup lang="ts">
import { USeparator } from '#components';
import ModeToggle from './ModeToggle.vue'
{{#if auth}}
import UserMenu from './UserMenu.vue'
{{/if}}
const links = [
{ to: "/", label: "Home" },
{{#if auth}}
{ to: "/dashboard", label: "Dashboard" },
{{/if}}
{{#if (includes examples "todo")}}
{ to: "/todos", label: "Todos" },
{{/if}}
{{#if (includes examples "ai")}}
{ to: "/ai", label: "AI Chat" },
{{/if}}
];
</script>
<template>
<div>
<div class="flex flex-row items-center justify-between px-2 py-1">
<nav class="flex gap-4 text-lg">
<NuxtLink
v-for="link in links"
:key="link.to"
:to="link.to"
class="text-foreground hover:text-primary"
active-class="text-primary font-semibold"
>
\{{ link.label }}
</NuxtLink>
</nav>
<div class="flex items-center gap-2">
<ModeToggle />
{{#if auth}}
<UserMenu />
{{/if}}
</div>
</div>
<USeparator />
</div>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<div class="flex h-full items-center justify-center pt-8">
<UIcon name="i-lucide-loader-2" class="animate-spin text-2xl" />
</div>
</template>

View File

@@ -0,0 +1,23 @@
<script setup lang="ts">
const colorMode = useColorMode()
const isDark = computed({
get () {
return colorMode.value === 'dark'
},
set (value) {
colorMode.preference = value ? 'dark' : 'light'
}
})
</script>
<template>
<div class="flex items-center">
<USwitch
v-model="isDark"
:checked-icon="isDark ? 'i-lucide-moon' : ''"
:unchecked-icon="!isDark ? 'i-lucide-sun' : ''"
class="mr-2"
/>
</div>
</template>

View File

@@ -0,0 +1,11 @@
<script setup>
</script>
<template>
<div class="grid grid-rows-[auto_1fr] h-full">
<Header />
<main class="overflow-y-auto">
<slot />
</main>
</div>
</template>

View File

@@ -0,0 +1,63 @@
<script setup lang="ts">
const { $orpc } = useNuxtApp()
import { useQuery } from '@tanstack/vue-query'
const TITLE_TEXT = `
██████╗ ███████╗████████╗████████╗███████╗██████╗
██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
████████╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
╚══██╔══╝ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
██║ ███████╗ ██║ ███████║██║ █████╔╝
██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
██║ ███████║ ██║ ██║ ██║╚██████╗██║ ██╗
╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
`;
const healthCheck = useQuery($orpc.healthCheck.queryOptions())
</script>
<template>
<div class="container mx-auto max-w-3xl px-4 py-2">
<pre class="overflow-x-auto font-mono text-sm whitespace-pre-wrap">{{ TITLE_TEXT }}</pre>
<div class="grid gap-6 mt-4">
<section class="rounded-lg border p-4">
<h2 class="mb-2 font-medium">API Status</h2>
<div class="flex items-center gap-2">
<div class="flex items-center gap-2">
<div
class="w-2 h-2 rounded-full"
:class="{
'bg-yellow-500 animate-pulse': healthCheck.status.value === 'pending',
'bg-green-500': healthCheck.status.value === 'success',
'bg-red-500': healthCheck.status.value === 'error',
'bg-gray-400': healthCheck.status.value !== 'pending' &&
healthCheck.status.value !== 'success' &&
healthCheck.status.value !== 'error'
}"
></div>
<span class="text-sm text-muted-foreground">
<template v-if="healthCheck.status.value === 'pending'">
Checking...
</template>
<template v-else-if="healthCheck.status.value === 'success'">
Connected ({{ healthCheck.data.value }})
</template>
<template v-else-if="healthCheck.status.value === 'error'">
Error: {{ healthCheck.error.value?.message || 'Failed to connect' }}
</template>
<template v-else>
Idle
</template>
</span>
</div>
</div>
</section>
</div>
</div>
</template>

View File

@@ -0,0 +1,44 @@
import type {
DehydratedState,
VueQueryPluginOptions,
} from '@tanstack/vue-query'
import {
dehydrate,
hydrate,
QueryCache,
QueryClient,
VueQueryPlugin,
} from '@tanstack/vue-query'
export default defineNuxtPlugin((nuxt) => {
const vueQueryState = useState<DehydratedState | null>('vue-query')
const toast = useToast()
const queryClient = new QueryClient({
queryCache: new QueryCache({
onError: (error) => {
console.log(error)
toast.add({
title: 'Error',
description: error?.message || 'An unexpected error occurred.',
})
},
}),
})
const options: VueQueryPluginOptions = { queryClient }
nuxt.vueApp.use(VueQueryPlugin, options)
if (import.meta.server) {
nuxt.hooks.hook('app:rendered', () => {
vueQueryState.value = dehydrate(queryClient)
})
}
if (import.meta.client) {
nuxt.hooks.hook('app:created', () => {
hydrate(queryClient, vueQueryState.value)
})
}
})

View File

@@ -0,0 +1,19 @@
// https://nuxt.com/docs/api/configuration/nuxt-config
export default defineNuxtConfig({
compatibilityDate: '2024-11-01',
future: {
compatibilityVersion: 4
},
devtools: { enabled: true },
modules: ['@nuxt/ui'],
css: ['~/assets/css/main.css'],
devServer: {
port: 3001
},
ssr: false,
runtimeConfig: {
public: {
serverURL: process.env.NUXT_PUBLIC_SERVER_URL,
}
}
})

View File

@@ -0,0 +1,25 @@
{
"name": "web",
"private": true,
"type": "module",
"scripts": {
"build": "nuxt build",
"dev": "nuxt dev",
"generate": "nuxt generate",
"preview": "nuxt preview",
"postinstall": "nuxt prepare"
},
"dependencies": {
"@nuxt/ui": "3.0.2",
"@tanstack/vue-query": "^5.74.5",
"nuxt": "^3.16.2",
"typescript": "^5.6.3",
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"zod": "^3.24.3"
},
"devDependencies": {
"@tanstack/vue-query-devtools": "^5.74.5",
"@iconify-json/lucide": "^1.2.38"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

View File

@@ -0,0 +1,2 @@
User-Agent: *
Disallow:

View File

@@ -0,0 +1,3 @@
{
"extends": "../.nuxt/tsconfig.server.json"
}

View File

@@ -0,0 +1,4 @@
{
// https://nuxt.com/docs/guide/concepts/typescript
"extends": "./.nuxt/tsconfig.json"
}

View File

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

View File

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 15 KiB