@reliverse/rse
Version:
@reliverse/rse is your all-in-one companion for bootstrapping and improving any kind of projects (especially web apps built with frameworks like Next.js) — whether you're kicking off something new or upgrading an existing app. It is also a little AI-power
1,658 lines (1,657 loc) • 201 kB
JavaScript
export const DLER_TPL_FRONTEND = {
name: "frontend",
description: "Template generated from 144 files",
updatedAt: "2025-06-17T20:33:59.810Z",
config: {
files: {
"frontend/native/native-base/assets/adaptive-icon.png": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "19b53640a9"
},
content: "",
type: "binary",
binaryHash: "19b53640a9"
},
"frontend/native/native-base/assets/favicon.png": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "cb25ca74dd"
},
content: "",
type: "binary",
binaryHash: "cb25ca74dd"
},
"frontend/native/native-base/assets/icon.png": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "3f71f5a845"
},
content: "",
type: "binary",
binaryHash: "3f71f5a845"
},
"frontend/native/native-base/assets/splash.png": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "206a875d5d"
},
content: "",
type: "binary",
binaryHash: "206a875d5d"
},
"frontend/native/nativewind/app/(drawer)/(tabs)/index.tsx": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "27a5056910"
},
content: `import { Container } from "@/components/container";
import { ScrollView, Text, View } from "react-native";
export default function TabOne() {
return (
<Container>
<ScrollView className="flex-1 p-6">
<View className="py-8">
<Text className="text-3xl font-bold text-foreground mb-2">
Tab One
</Text>
<Text className="text-lg text-muted-foreground">
Explore the first section of your app
</Text>
</View>
</ScrollView>
</Container>
);
}
`,
type: "text"
},
"frontend/native/nativewind/app/(drawer)/(tabs)/two.tsx": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "f53538fbaa"
},
content: `import { Container } from "@/components/container";
import { ScrollView, Text, View } from "react-native";
export default function TabTwo() {
return (
<Container>
<ScrollView className="flex-1 p-6">
<View className="py-8">
<Text className="text-3xl font-bold text-foreground mb-2">
Tab Two
</Text>
<Text className="text-lg text-muted-foreground">
Discover more features and content
</Text>
</View>
</ScrollView>
</Container>
);
}
`,
type: "text"
},
"frontend/native/nativewind/app/(drawer)/(tabs)/_layout.tsx": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "dfbb2c9f9e"
},
content: `import { TabBarIcon } from "@/components/tabbar-icon";
import { useColorScheme } from "@/lib/use-color-scheme";
import { Tabs } from "expo-router";
export default function TabLayout() {
const { isDarkColorScheme } = useColorScheme();
return (
<Tabs
screenOptions={{
headerShown: false,
tabBarActiveTintColor: isDarkColorScheme
? "hsl(217.2 91.2% 59.8%)"
: "hsl(221.2 83.2% 53.3%)",
tabBarInactiveTintColor: isDarkColorScheme
? "hsl(215 20.2% 65.1%)"
: "hsl(215.4 16.3% 46.9%)",
tabBarStyle: {
backgroundColor: isDarkColorScheme
? "hsl(222.2 84% 4.9%)"
: "hsl(0 0% 100%)",
borderTopColor: isDarkColorScheme
? "hsl(217.2 32.6% 17.5%)"
: "hsl(214.3 31.8% 91.4%)",
},
}}
>
<Tabs.Screen
name="index"
options={{
title: "Home",
tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />,
}}
/>
<Tabs.Screen
name="two"
options={{
title: "Explore",
tabBarIcon: ({ color }) => (
<TabBarIcon name="compass" color={color} />
),
}}
/>
</Tabs>
);
}
`,
type: "text"
},
"frontend/native/nativewind/app/(drawer)/index.tsx.hbs": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "376c48bfd1"
},
content: `import { View, Text, ScrollView } from "react-native";
import { Container } from "@/components/container";
{{#if (eq api "orpc")}}
import { useQuery } from "@tanstack/react-query";
import { orpc } from "@/utils/orpc";
{{/if}}
{{#if (eq api "trpc")}}
import { useQuery } from "@tanstack/react-query";
import { trpc } from "@/utils/trpc";
{{/if}}
{{#if (eq backend "convex")}}
import { useQuery } from "convex/react";
import { api } from "@{{ projectName }}/backend/convex/_generated/api";
{{/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}}
{{#if (eq backend "convex")}}
const healthCheck = useQuery(api.healthCheck.get);
{{/if}}
return (
<Container>
<ScrollView showsVerticalScrollIndicator={false} className="flex-1">
<Text className="font-mono text-foreground text-3xl font-bold mb-4">
BETTER T STACK
</Text>
<View className="bg-card border border-border rounded-xl p-6 mb-6 shadow-sm">
{{#if (eq backend "convex")}}
<View className="flex-row items-center gap-3">
<View
className={\`h-3 w-3 rounded-full \${
healthCheck ? "bg-green-500" : "bg-orange-500"
}\`}
/>
<View className="flex-1">
<Text className="text-sm font-medium text-card-foreground">
Convex
</Text>
<Text className="text-xs text-muted-foreground">
{healthCheck === undefined
? "Checking connection..."
: healthCheck === "OK"
? "All systems operational"
: "Service unavailable"}
</Text>
</View>
</View>
{{else}}
{{#unless (eq api "none")}}
<View className="flex-row items-center gap-3">
<View
className={\`h-3 w-3 rounded-full \${
healthCheck.data ? "bg-green-500" : "bg-orange-500"
}\`}
/>
<View className="flex-1">
<Text className="text-sm font-medium text-card-foreground">
{{#if (eq api "orpc")}}
ORPC
{{/if}}
{{#if (eq api "trpc")}}
TRPC
{{/if}}
</Text>
<Text className="text-xs text-muted-foreground">
{{#if (eq api "orpc")}}
{healthCheck.isLoading
? "Checking connection..."
: healthCheck.data
? "All systems operational"
: "Service unavailable"}
{{/if}}
{{#if (eq api "trpc")}}
{healthCheck.isLoading
? "Checking connection..."
: healthCheck.data
? "All systems operational"
: "Service unavailable"}
{{/if}}
</Text>
</View>
</View>
{{/unless}}
{{/if}}
</View>
</ScrollView>
</Container>
);
}
`,
type: "text"
},
"frontend/native/nativewind/app/(drawer)/_layout.tsx.hbs": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "9f2afabebd"
},
content: `import { Ionicons, MaterialIcons } from "@expo/vector-icons";
import { Link } from "expo-router";
import { Drawer } from "expo-router/drawer";
import { HeaderButton } from "@/components/header-button";
const DrawerLayout = () => {
return (
<Drawer>
<Drawer.Screen
name="index"
options={{
headerTitle: "Home",
drawerLabel: "Home",
drawerIcon: ({ size, color }) => (
<Ionicons name="home-outline" size={size} color={color} />
),
}}
/>
<Drawer.Screen
name="(tabs)"
options={{
headerTitle: "Tabs",
drawerLabel: "Tabs",
drawerIcon: ({ size, color }) => (
<MaterialIcons name="border-bottom" size={size} color={color} />
),
headerRight: () => (
<Link href="/modal" asChild>
<HeaderButton />
</Link>
),
}}
/>
{{#if (includes examples "todo")}}
<Drawer.Screen
name="todos"
options={{
headerTitle: "Todos",
drawerLabel: "Todos",
drawerIcon: ({ size, color }) => (
<Ionicons name="checkbox-outline" size={size} color={color} />
),
}}
/>
{{/if}}
{{#if (includes examples "ai")}}
<Drawer.Screen
name="ai"
options={{
headerTitle: "AI",
drawerLabel: "AI",
drawerIcon: ({ size, color }) => (
<Ionicons
name="chatbubble-ellipses-outline"
size={size}
color={color}
/>
),
}}
/>
{{/if}}
</Drawer>
);
};
export default DrawerLayout;
`,
type: "text"
},
"frontend/native/nativewind/app/+html.tsx": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "01345c626a"
},
content: `import { ScrollViewStyleReset } from 'expo-router/html';
import { ReactNode } from 'react';
// This file is web-only and used to configure the root HTML for every
// web page during static rendering.
// The contents of this function only run in Node.js environments and
// do not have access to the DOM or browser APIs.
export default function Root({ children }: { children: ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta content="IE=edge" httpEquiv="X-UA-Compatible" />
{/*
This viewport disables scaling which makes the mobile website act more like a native app.
However this does reduce built-in accessibility. If you want to enable scaling, use this instead:
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
*/}
<meta
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1.00001,viewport-fit=cover"
name="viewport"
/>
{/*
Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
*/}
<ScrollViewStyleReset />
{/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */}
<style dangerouslySetInnerHTML={{ __html: responsiveBackground }} />
{/* Add any additional <head> elements that you want globally available on web... */}
</head>
<body>{children}</body>
</html>
);
}
const responsiveBackground = \`
body {
background-color: #fff;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #000;
}
}\`;
`,
type: "text"
},
"frontend/native/nativewind/app/+not-found.tsx": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "c9300cea7e"
},
content: `import { Container } from "@/components/container";
import { Link, Stack } from "expo-router";
import { Text, View } from "react-native";
export default function NotFoundScreen() {
return (
<>
<Stack.Screen options={{ title: "Oops!" }} />
<Container>
<View className="flex-1 justify-center items-center p-6">
<View className="items-center">
<Text className="text-6xl mb-4">\u{1F914}</Text>
<Text className="text-2xl font-bold text-foreground mb-2 text-center">
Page Not Found
</Text>
<Text className="text-muted-foreground text-center mb-8 max-w-sm">
Sorry, the page you're looking for doesn't exist.
</Text>
<Link href="/" asChild>
<Text className="text-primary font-medium bg-primary/10 px-6 py-3 rounded-lg">
Go to Home
</Text>
</Link>
</View>
</View>
</Container>
</>
);
}
`,
type: "text"
},
"frontend/native/nativewind/app/modal.tsx": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "61c53a4a93"
},
content: `import { Container } from "@/components/container";
import { Text, View } from "react-native";
export default function Modal() {
return (
<Container>
<View className="flex-1 p-6">
<View className="flex-row items-center justify-between mb-8">
<Text className="text-2xl font-bold text-foreground">Modal</Text>
</View>
</View>
</Container>
);
}
`,
type: "text"
},
"frontend/native/nativewind/app/_layout.tsx.hbs": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "22be538e23"
},
content: `{{#if (includes examples "ai")}}
import "@/polyfills";
{{/if}}
{{#if (eq backend "convex")}}
import { ConvexProvider, ConvexReactClient } from "convex/react";
{{else}}
{{#unless (eq api "none")}}
import { QueryClientProvider } from "@tanstack/react-query";
{{/unless}}
{{/if}}
import { Stack } from "expo-router";
import {
DarkTheme,
DefaultTheme,
type Theme,
ThemeProvider,
} from "@react-navigation/native";
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";
import { Platform } from "react-native";
import { setAndroidNavigationBar } from "@/lib/android-navigation-bar";
const LIGHT_THEME: Theme = {
...DefaultTheme,
colors: NAV_THEME.light,
};
const DARK_THEME: Theme = {
...DarkTheme,
colors: NAV_THEME.dark,
};
export const unstable_settings = {
initialRouteName: "(drawer)",
};
{{#if (eq backend "convex")}}
const convex = new ConvexReactClient(process.env.EXPO_PUBLIC_CONVEX_URL!, {
unsavedChangesWarning: false,
});
{{/if}}
export default function RootLayout() {
const hasMounted = useRef(false);
const { colorScheme, isDarkColorScheme } = useColorScheme();
const [isColorSchemeLoaded, setIsColorSchemeLoaded] = React.useState(false);
useIsomorphicLayoutEffect(() => {
if (hasMounted.current) {
return;
}
if (Platform.OS === "web") {
document.documentElement.classList.add("bg-background");
}
setAndroidNavigationBar(colorScheme);
setIsColorSchemeLoaded(true);
hasMounted.current = true;
}, []);
if (!isColorSchemeLoaded) {
return null;
}
return (
{{#if (eq backend "convex")}}
<ConvexProvider client={convex}>
<ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
<StatusBar style={isDarkColorScheme ? "light" : "dark"} />
<GestureHandlerRootView style={{ flex: 1 }}>
<Stack>
<Stack.Screen name="(drawer)" options={{ headerShown: false }} />
<Stack.Screen
name="modal"
options={{ title: "Modal", presentation: "modal" }}
/>
</Stack>
</GestureHandlerRootView>
</ThemeProvider>
</ConvexProvider>
{{else}}
{{#unless (eq api "none")}}
<QueryClientProvider client={queryClient}>
<ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
<StatusBar style={isDarkColorScheme ? "light" : "dark"} />
<GestureHandlerRootView style={{ flex: 1 }}>
<Stack>
<Stack.Screen name="(drawer)" options={{ headerShown: false }} />
<Stack.Screen
name="modal"
options={{ title: "Modal", presentation: "modal" }}
/>
</Stack>
</GestureHandlerRootView>
</ThemeProvider>
</QueryClientProvider>
{{else}}
<ThemeProvider value={isDarkColorScheme ? DARK_THEME : LIGHT_THEME}>
<StatusBar style={isDarkColorScheme ? "light" : "dark"} />
<GestureHandlerRootView style={{ flex: 1 }}>
<Stack>
<Stack.Screen name="(drawer)" options={{ headerShown: false }} />
<Stack.Screen
name="modal"
options={{ title: "Modal", presentation: "modal" }}
/>
</Stack>
</GestureHandlerRootView>
</ThemeProvider>
{{/unless}}
{{/if}}
);
}
const useIsomorphicLayoutEffect =
Platform.OS === "web" && typeof window === "undefined"
? React.useEffect
: React.useLayoutEffect;
`,
type: "text"
},
"frontend/native/nativewind/app-env.d.ts": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "0309e174a4"
},
content: `// @ts-ignore
/// <reference types="nativewind/types" />
`,
type: "text"
},
"frontend/native/nativewind/app.json": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "a33bfebe86"
},
content: {
expo: {
name: "my-better-t-app",
slug: "my-better-t-app",
version: "1.0.0",
scheme: "my-better-t-app",
web: {
bundler: "metro",
output: "static",
favicon: "./assets/favicon.png"
},
plugins: ["expo-router", "expo-secure-store", "expo-web-browser"],
experiments: {
typedRoutes: true,
tsconfigPaths: true
},
newArchEnabled: true,
orientation: "portrait",
icon: "./assets/icon.png",
userInterfaceStyle: "light",
splash: {
image: "./assets/splash.png",
resizeMode: "contain",
backgroundColor: "#ffffff"
},
assetBundlePatterns: ["**/*"],
ios: {
supportsTablet: true,
bundleIdentifier: "com.amanvarshney01.mybettertapp"
},
android: {
adaptiveIcon: {
foregroundImage: "./assets/adaptive-icon.png",
backgroundColor: "#ffffff"
},
package: "com.amanvarshney01.mybettertapp",
edgeToEdgeEnabled: true
}
}
},
type: "json"
},
"frontend/native/nativewind/babel.config.js": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "6b22e013c8"
},
content: `module.exports = function (api) {
api.cache(true);
const plugins = [];
plugins.push('react-native-reanimated/plugin');
return {
presets: [['babel-preset-expo', { jsxImportSource: 'nativewind' }], 'nativewind/babel'],
plugins,
};
};
`,
type: "text"
},
"frontend/native/nativewind/components/container.tsx": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "7a9ea19616"
},
content: `import React from "react";
import { SafeAreaView } from "react-native";
export const Container = ({ children }: { children: React.ReactNode }) => {
return (
<SafeAreaView className="flex-1 bg-background">{children}</SafeAreaView>
);
};
`,
type: "text"
},
"frontend/native/nativewind/components/header-button.tsx": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "ea442dd11d"
},
content: `import FontAwesome from "@expo/vector-icons/FontAwesome";
import { forwardRef } from "react";
import { Pressable } from "react-native";
export const HeaderButton = forwardRef<
typeof Pressable,
{ onPress?: () => void }
>(({ onPress }, ref) => {
return (
<Pressable
onPress={onPress}
className="p-2 mr-2 rounded-lg bg-secondary/50 active:bg-secondary"
>
{({ pressed }) => (
<FontAwesome
name="info-circle"
size={20}
className="text-secondary-foreground"
style={{
opacity: pressed ? 0.7 : 1,
}}
/>
)}
</Pressable>
);
});
`,
type: "text"
},
"frontend/native/nativewind/components/tabbar-icon.tsx": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "ae6a7e3fb3"
},
content: `import FontAwesome from "@expo/vector-icons/FontAwesome";
export const TabBarIcon = (props: {
name: React.ComponentProps<typeof FontAwesome>["name"];
color: string;
}) => {
return <FontAwesome size={24} style={{ marginBottom: -3 }} {...props} />;
};
`,
type: "text"
},
"frontend/native/nativewind/global.css": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "c42b9336d6"
},
content: `@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96%;
--secondary-foreground: 222.2 84% 4.9%;
--muted: 210 40% 96%;
--muted-foreground: 215.4 16.3% 40%;
--accent: 210 40% 96%;
--accent-foreground: 222.2 84% 4.9%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 8px;
}
.dark:root {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 84% 4.9%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 70%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 72% 51%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 224.3 76.3% 94.1%;
}
}
`,
type: "text"
},
"frontend/native/nativewind/lib/android-navigation-bar.tsx": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "fcd7da609f"
},
content: `import * as NavigationBar from "expo-navigation-bar";
import { Platform } from "react-native";
import { NAV_THEME } from "@/lib/constants";
export async function setAndroidNavigationBar(theme: "light" | "dark") {
if (Platform.OS !== "android") return;
await NavigationBar.setButtonStyleAsync(theme === "dark" ? "light" : "dark");
await NavigationBar.setBackgroundColorAsync(
theme === "dark" ? NAV_THEME.dark.background : NAV_THEME.light.background,
);
}
`,
type: "text"
},
"frontend/native/nativewind/lib/constants.ts": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "308bab1835"
},
content: `export const NAV_THEME = {
light: {
background: "hsl(0 0% 100%)",
border: "hsl(220 13% 91%)",
card: "hsl(0 0% 100%)",
notification: "hsl(0 84.2% 60.2%)",
primary: "hsl(221.2 83.2% 53.3%)",
text: "hsl(222.2 84% 4.9%)",
},
dark: {
background: "hsl(222.2 84% 4.9%)",
border: "hsl(217.2 32.6% 17.5%)",
card: "hsl(222.2 84% 4.9%)",
notification: "hsl(0 72% 51%)",
primary: "hsl(217.2 91.2% 59.8%)",
text: "hsl(210 40% 98%)",
},
};
`,
type: "text"
},
"frontend/native/nativewind/lib/use-color-scheme.ts": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "a104869d85"
},
content: `import { useColorScheme as useNativewindColorScheme } from "nativewind";
export function useColorScheme() {
const { colorScheme, setColorScheme, toggleColorScheme } =
useNativewindColorScheme();
return {
colorScheme: colorScheme ?? "dark",
isDarkColorScheme: colorScheme === "dark",
setColorScheme,
toggleColorScheme,
};
}
`,
type: "text"
},
"frontend/native/nativewind/metro.config.js": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "59a0d5ffd6"
},
content: `// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require("expo/metro-config");
const { FileStore } = require("metro-cache");
const { withNativeWind } = require("nativewind/metro");
const path = require("node:path");
const config = withTurborepoManagedCache(
withMonorepoPaths(
withNativeWind(getDefaultConfig(__dirname), {
input: "./global.css",
configPath: "./tailwind.config.js",
}),
),
);
config.resolver.unstable_enablePackageExports = true;
config.resolver.disableHierarchicalLookup = true;
module.exports = config;
/**
* Add the monorepo paths to the Metro config.
* This allows Metro to resolve modules from the monorepo.
*
* @see https://docs.expo.dev/guides/monorepos/#modify-the-metro-config
* @param {import('expo/metro-config').MetroConfig} config
* @returns {import('expo/metro-config').MetroConfig}
*/
function withMonorepoPaths(config) {
const projectRoot = __dirname;
const workspaceRoot = path.resolve(projectRoot, "../..");
// #1 - Watch all files in the monorepo
config.watchFolders = [workspaceRoot];
// #2 - Resolve modules within the project's \`node_modules\` first, then all monorepo modules
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, "node_modules"),
path.resolve(workspaceRoot, "node_modules"),
];
return config;
}
/**
* Move the Metro cache to the \`.cache/metro\` folder.
* If you have any environment variables, you can configure Turborepo to invalidate it when needed.
*
* @see https://turbo.build/repo/docs/reference/configuration#env
* @param {import('expo/metro-config').MetroConfig} config
* @returns {import('expo/metro-config').MetroConfig}
*/
function withTurborepoManagedCache(config) {
config.cacheStores = [
new FileStore({ root: path.join(__dirname, ".cache/metro") }),
];
return config;
}
`,
type: "text"
},
"frontend/native/nativewind/package.json.hbs": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "f54263cddd"
},
content: `{
"name": "native",
"version": "1.0.0",
"main": "expo-router/entry",
"scripts": {
"dev": "expo start --clear",
"android": "expo run:android",
"ios": "expo run:ios",
"prebuild": "expo prebuild",
"web": "expo start --web"
},
"dependencies": {
"@expo/vector-icons": "^14.0.4",
"@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/drawer": "^7.1.1",
"@react-navigation/native": "^7.0.14",
"@tanstack/react-form": "^1.0.5",
"@tanstack/react-query": "^5.69.2",
{{#if (includes examples "ai")}}
"@stardazed/streams-text-encoding": "^1.0.2",
"@ungap/structured-clone": "^1.3.0",
{{/if}}
"expo": "^53.0.4",
"expo-constants": "~17.1.4",
"expo-linking": "~7.1.4",
"expo-navigation-bar": "~4.2.3",
"expo-router": "~5.0.3",
"expo-secure-store": "~14.2.3",
"expo-status-bar": "~2.2.3",
"expo-system-ui": "~5.0.6",
"expo-web-browser": "~14.1.6",
"nativewind": "^4.1.23",
"react": "19.0.0",
"react-dom": "19.0.0",
"react-native": "0.79.1",
"react-native-gesture-handler": "~2.24.0",
"react-native-reanimated": "~3.17.4",
"react-native-safe-area-context": "5.3.0",
"react-native-screens": "~4.10.0",
"react-native-web": "^0.20.0"
},
"devDependencies": {
"@babel/core": "^7.26.10",
"@types/react": "~19.0.10",
"tailwindcss": "^3.4.17",
"typescript": "~5.8.2"
},
"private": true
}
`,
type: "text"
},
"frontend/native/nativewind/tailwind.config.js": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "7ac6f20414"
},
content: `const { hairlineWidth } = require("nativewind/theme");
/** @type {import('tailwindcss').Config} */
module.exports = {
darkMode: "class",
content: ["./app/**/*.{js,ts,tsx}", "./components/**/*.{js,ts,tsx}"],
presets: [require("nativewind/preset")],
theme: {
extend: {
colors: {
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
},
secondary: {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
},
accent: {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
radius: "var(--radius)",
},
borderRadius: {
xl: "calc(var(--radius) + 4px)",
lg: "var(--radius)",
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
borderWidth: {
hairline: hairlineWidth(),
},
},
},
plugins: [],
};
`,
type: "text"
},
"frontend/native/nativewind/tsconfig.json.hbs": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "d135042b9e"
},
content: `{
"extends": "expo/tsconfig.base",
"compilerOptions": {
"strict": true,
"paths": {
"@/*": [
"./*"
]
}
},
"include": [
"**/*.ts",
"**/*.tsx",
".expo/types/**/*.ts",
"expo-env.d.ts",
"nativewind-env.d.ts"
],
{{#unless (or (eq backend "convex") (eq backend "none"))}}
"references": [{
"path": "../server"
}]
{{/unless}}
}
`,
type: "text"
},
"frontend/native/nativewind/_gitignore": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "dc295f33bd"
},
content: `node_modules/
.expo/
dist/
npm-debug.*
*.jks
*.p8
*.p12
*.key
*.mobileprovision
*.orig.*
web-build/
# expo router
expo-env.d.ts
.env
.cache
ios
android
# macOS
.DS_Store
# Temporary files created by Metro to check the health of the file watcher
.metro-health-check*
`,
type: "text"
},
"frontend/native/unistyles/app/(drawer)/(tabs)/index.tsx": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "305e0922ef"
},
content: `import { Container } from "@/components/container";
import { ScrollView, Text, View } from "react-native";
import { StyleSheet } from "react-native-unistyles";
export default function Home() {
return (
<Container>
<ScrollView contentContainerStyle={styles.container}>
<View style={styles.headerSection}>
<Text style={styles.title}>Tab One</Text>
<Text style={styles.subtitle}>
Explore the first section of your app
</Text>
</View>
</ScrollView>
</Container>
);
}
const styles = StyleSheet.create((theme) => ({
container: {
padding: theme.spacing.lg,
},
headerSection: {
paddingVertical: theme.spacing.xl,
},
title: {
fontSize: theme.fontSize["3xl"],
fontWeight: "bold",
color: theme.colors.foreground,
marginBottom: theme.spacing.sm,
},
subtitle: {
fontSize: theme.fontSize.lg,
color: theme.colors.mutedForeground,
},
}));
`,
type: "text"
},
"frontend/native/unistyles/app/(drawer)/(tabs)/two.tsx": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "d81d62ca2c"
},
content: `import { Container } from "@/components/container";
import { ScrollView, Text, View } from "react-native";
import { StyleSheet } from "react-native-unistyles";
export default function TabTwo() {
return (
<Container>
<ScrollView contentContainerStyle={styles.container}>
<View style={styles.headerSection}>
<Text style={styles.title}>Tab Two</Text>
<Text style={styles.subtitle}>
Discover more features and content
</Text>
</View>
</ScrollView>
</Container>
);
}
const styles = StyleSheet.create((theme) => ({
container: {
padding: theme.spacing.lg,
},
headerSection: {
paddingVertical: theme.spacing.xl,
},
title: {
fontSize: theme.fontSize["3xl"],
fontWeight: "bold",
color: theme.colors.foreground,
marginBottom: theme.spacing.sm,
},
subtitle: {
fontSize: theme.fontSize.lg,
color: theme.colors.mutedForeground,
},
}));
`,
type: "text"
},
"frontend/native/unistyles/app/(drawer)/(tabs)/_layout.tsx": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "37617a510a"
},
content: `import { Tabs } from "expo-router";
import { useUnistyles } from "react-native-unistyles";
import { TabBarIcon } from "@/components/tabbar-icon";
export default function TabLayout() {
const { theme } = useUnistyles();
return (
<Tabs
screenOptions={{
headerShown: false,
tabBarActiveTintColor: theme.colors.primary,
tabBarInactiveTintColor: theme.colors.mutedForeground,
tabBarStyle: {
backgroundColor: theme.colors.background,
borderTopColor: theme.colors.border,
},
}}
>
<Tabs.Screen
name="index"
options={{
title: "Home",
tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />,
}}
/>
<Tabs.Screen
name="two"
options={{
title: "Explore",
tabBarIcon: ({ color }) => (
<TabBarIcon name="compass" color={color} />
),
}}
/>
</Tabs>
);
}
`,
type: "text"
},
"frontend/native/unistyles/app/(drawer)/index.tsx.hbs": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "b76ab44b56"
},
content: `import { ScrollView, Text, View } from "react-native";
import { StyleSheet } from "react-native-unistyles";
import { Container } from "@/components/container";
{{#if (eq api "orpc")}}
import { useQuery } from "@tanstack/react-query";
import { orpc } from "@/utils/orpc";
{{/if}}
{{#if (eq api "trpc")}}
import { useQuery } from "@tanstack/react-query";
import { trpc } from "@/utils/trpc";
{{/if}}
{{#if (eq backend "convex")}}
import { useQuery } from "convex/react";
import { api } from "@{{ projectName }}/backend/convex/_generated/api";
{{/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}}
{{#if (eq backend "convex")}}
const healthCheck = useQuery(api.healthCheck.get);
{{/if}}
return (
<Container>
<ScrollView
contentContainerStyle={styles.container}
showsVerticalScrollIndicator={false}
>
<Text className="font-mono text-foreground text-3xl font-bold mb-4">
BETTER T STACK
</Text>
<View style={styles.statusCard}>
<View style={styles.statusHeader}>
<Text style={styles.statusTitle}>System Status</Text>
<View style={styles.statusBadge}>
<Text style={styles.statusBadgeText}>LIVE</Text>
</View>
</View>
{{#if (eq backend "convex")}}
<View style={styles.statusRow}>
<View
style={[
styles.statusDot,
healthCheck === "OK"
? styles.statusDotSuccess
: styles.statusDotWarning,
]}
/>
<View style={styles.statusContent}>
<Text style={styles.statusLabel}>Convex</Text>
<Text style={styles.statusDescription}>
{healthCheck === undefined
? "Checking connection..."
: healthCheck === "OK"
? "All systems operational"
: "Service unavailable"}
</Text>
</View>
</View>
{{else}}
{{#unless (eq api "none")}}
<View style={styles.statusRow}>
<View
style={[
styles.statusDot,
healthCheck.data
? styles.statusDotSuccess
: styles.statusDotWarning,
]}
/>
<View style={styles.statusContent}>
<Text style={styles.statusLabel}>
{{#if (eq api "orpc")}}
ORPC
{{/if}}
{{#if (eq api "trpc")}}
TRPC
{{/if}}
</Text>
<Text style={styles.statusDescription}>
{{#if (eq api "orpc")}}
{healthCheck.isLoading
? "Checking connection..."
: healthCheck.data
? "All systems operational"
: "Service unavailable"}
{{/if}}
{{#if (eq api "trpc")}}
{healthCheck.isLoading
? "Checking connection..."
: healthCheck.data
? "All systems operational"
: "Service unavailable"}
{{/if}}
</Text>
</View>
</View>
{{/unless}}
{{/if}}
</View>
</ScrollView>
</Container>
);
}
const styles = StyleSheet.create((theme) => ({
container: {
paddingHorizontal: theme.spacing.md,
},
heroSection: {
paddingVertical: theme.spacing.xl,
},
heroTitle: {
fontSize: theme.fontSize["4xl"],
fontWeight: "bold",
color: theme.colors.foreground,
marginBottom: theme.spacing.sm,
},
heroSubtitle: {
fontSize: theme.fontSize.lg,
color: theme.colors.mutedForeground,
lineHeight: 28,
},
statusCard: {
backgroundColor: theme.colors.card,
borderWidth: 1,
borderColor: theme.colors.border,
borderRadius: theme.borderRadius.xl,
padding: theme.spacing.lg,
marginBottom: theme.spacing.lg,
shadowColor: "#000",
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.05,
shadowRadius: 3,
elevation: 2,
},
statusHeader: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
marginBottom: theme.spacing.md,
},
statusTitle: {
fontSize: theme.fontSize.lg,
fontWeight: "600",
color: theme.colors.cardForeground,
},
statusBadge: {
backgroundColor: theme.colors.secondary,
paddingHorizontal: theme.spacing.sm + 4,
paddingVertical: theme.spacing.xs,
borderRadius: 9999,
},
statusBadgeText: {
fontSize: theme.fontSize.xs,
fontWeight: "500",
color: theme.colors.secondaryForeground,
},
statusRow: {
flexDirection: "row",
alignItems: "center",
gap: theme.spacing.sm + 4,
},
statusDot: {
height: 12,
width: 12,
borderRadius: 6,
},
statusDotSuccess: {
backgroundColor: theme.colors.success,
},
statusDotWarning: {
backgroundColor: "#F59E0B",
},
statusContent: {
flex: 1,
},
statusLabel: {
fontSize: theme.fontSize.sm,
fontWeight: "500",
color: theme.colors.cardForeground,
},
statusDescription: {
fontSize: theme.fontSize.xs,
color: theme.colors.mutedForeground,
}
}));
`,
type: "text"
},
"frontend/native/unistyles/app/(drawer)/_layout.tsx.hbs": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "825ba9cffb"
},
content: `import { Ionicons, MaterialIcons } from "@expo/vector-icons";
import { Link } from "expo-router";
import { Drawer } from "expo-router/drawer";
import { useUnistyles } from "react-native-unistyles";
import { HeaderButton } from "../../components/header-button";
const DrawerLayout = () => {
const { theme } = useUnistyles();
return (
<Drawer
screenOptions={{
headerStyle: {
backgroundColor: theme.colors.background,
},
headerTitleStyle: {
color: theme.colors.foreground,
},
headerTintColor: theme.colors.foreground,
drawerStyle: {
backgroundColor: theme.colors.background,
},
drawerLabelStyle: {
color: theme.colors.foreground,
},
drawerInactiveTintColor: theme.colors.mutedForeground,
}}
>
<Drawer.Screen
name="index"
options={{
headerTitle: "Home",
drawerLabel: "Home",
drawerIcon: ({ size, color }) => (
<Ionicons name="home-outline" size={size} color={color} />
),
}}
/>
<Drawer.Screen
name="(tabs)"
options={{
headerTitle: "Tabs",
drawerLabel: "Tabs",
drawerIcon: ({ size, color }) => (
<MaterialIcons name="border-bottom" size={size} color={color} />
),
headerRight: () => (
<Link href="/modal" asChild>
<HeaderButton />
</Link>
),
}}
/>
{{#if (includes examples "todo")}}
<Drawer.Screen
name="todos"
options={{
headerTitle: "Todos",
drawerLabel: "Todos",
drawerIcon: ({ size, color }) => (
<Ionicons name="checkbox-outline" size={size} color={color} />
),
}}
/>
{{/if}}
{{#if (includes examples "ai")}}
<Drawer.Screen
name="ai"
options={{
headerTitle: "AI",
drawerLabel: "AI",
drawerIcon: ({ size, color }) => (
<Ionicons
name="chatbubble-ellipses-outline"
size={size}
color={color}
/>
),
}}
/>
{{/if}}
</Drawer>
);
};
export default DrawerLayout;
`,
type: "text"
},
"frontend/native/unistyles/app/+html.tsx": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "390b55789f"
},
content: `import { ScrollViewStyleReset } from 'expo-router/html';
import '../unistyles';
// This file is web-only and used to configure the root HTML for every
// web page during static rendering.
// The contents of this function only run in Node.js environments and
// do not have access to the DOM or browser APIs.
export default function Root({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<head>
<meta charSet="utf-8" />
<meta httpEquiv="X-UA-Compatible" content="IE=edge" />
{/*
This viewport disables scaling which makes the mobile website act more like a native app.
However this does reduce built-in accessibility. If you want to enable scaling, use this instead:
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
*/}
<meta
name="viewport"
content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1.00001,viewport-fit=cover"
/>
{/*
Disable body scrolling on web. This makes ScrollView components work closer to how they do on native.
However, body scrolling is often nice to have for mobile web. If you want to enable it, remove this line.
*/}
<ScrollViewStyleReset />
{/* Using raw CSS styles as an escape-hatch to ensure the background color never flickers in dark-mode. */}
<style dangerouslySetInnerHTML={{ __html: responsiveBackground }} />
{/* Add any additional <head> elements that you want globally available on web... */}
</head>
<body>{children}</body>
</html>
);
}
const responsiveBackground = \`
body {
background-color: #fff;
}
@media (prefers-color-scheme: dark) {
body {
background-color: #000;
}
}\`;
`,
type: "text"
},
"frontend/native/unistyles/app/+not-found.tsx": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "5b81a69416"
},
content: `import { Container } from "@/components/container";
import { Link, Stack } from "expo-router";
import { Text, View } from "react-native";
import { StyleSheet } from "react-native-unistyles";
export default function NotFoundScreen() {
return (
<>
<Stack.Screen options={{ title: "Oops!" }} />
<Container>
<View style={styles.container}>
<View style={styles.content}>
<Text style={styles.emoji}>\u{1F914}</Text>
<Text style={styles.title}>Page Not Found</Text>
<Text style={styles.description}>
Sorry, the page you're looking for doesn't exist.
</Text>
<Link href="/" style={styles.button}>
<Text style={styles.buttonText}>Go to Home</Text>
</Link>
</View>
</View>
</Container>
</>
);
}
const styles = StyleSheet.create((theme) => ({
container: {
flex: 1,
justifyContent: "center",
alignItems: "center",
padding: theme.spacing.lg,
},
content: {
alignItems: "center",
},
emoji: {
fontSize: 64,
marginBottom: theme.spacing.md,
},
title: {
fontSize: theme.fontSize["2xl"],
fontWeight: "bold",
color: theme.colors.foreground,
marginBottom: theme.spacing.sm,
textAlign: "center",
},
description: {
color: theme.colors.mutedForeground,
textAlign: "center",
marginBottom: theme.spacing.xl,
maxWidth: 280,
},
button: {
backgroundColor: \`\${theme.colors.primary}1A\`, // 10% opacity
paddingHorizontal: theme.spacing.lg,
paddingVertical: theme.spacing.sm + 4,
borderRadius: theme.borderRadius.lg,
},
buttonText: {
color: theme.colors.primary,
fontWeight: "500",
},
}));
`,
type: "text"
},
"frontend/native/unistyles/app/modal.tsx": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "a7e295903b"
},
content: `import { Container } from "@/components/container";
import { Text, View } from "react-native";
import { StyleSheet } from "react-native-unistyles";
export default function Modal() {
return (
<Container>
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.title}>Modal</Text>
</View>
</View>
</Container>
);
}
const styles = StyleSheet.create((theme) => ({
container: {
flex: 1,
padding: theme.spacing.lg,
},
header: {
flexDirection: "row",
alignItems: "center",
justifyContent: "space-between",
marginBottom: theme.spacing.xl,
},
title: {
fontSize: theme.fontSize["2xl"],
fontWeight: "bold",
color: theme.colors.foreground,
},
}));
`,
type: "text"
},
"frontend/native/unistyles/app/_layout.tsx.hbs": {
metadata: {
updatedAt: "2025-06-17T06:06:35.000Z",
updatedHash: "57f8a73570"
},
content: `{{#if (includes examples "ai")}}
import "@/polyfills";
{{/if}}
{{#if (eq api "trpc")}}
import { queryClient } from "@/utils/trpc";
{{/if}}
{{#if (eq api "orpc")}}
import { queryClient } from "@/utils/orpc";
{{/if}}
{{#if (eq backend "convex")}}
import { ConvexProvider, ConvexReactClient } from "convex/react";
{{else}}
{{#unless (eq api "none")}}
import { QueryClientProvider } from "@tanstack/react-query";
{{/unless}}
{{/if}}
import { Stack } from "expo-router";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { useUnistyles } from "react-native-unistyles";
export const unstable_settings = {
// Ensure that reloading on \`/modal\` keeps a back button present.
initialRouteName: "(drawer)",
};
{{#if (eq backend "convex")}}