feat(cli): upgrade to expo 54 (#574)

This commit is contained in:
Aman Varshney
2025-09-12 00:11:41 +05:30
committed by GitHub
parent 0bfb3cfda0
commit 09482029c5
68 changed files with 491 additions and 579 deletions

View File

@@ -1,90 +1,90 @@
{ {
"name": "create-better-t-stack", "name": "create-better-t-stack",
"version": "2.44.0", "version": "2.44.0",
"description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations", "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
"type": "module", "type": "module",
"license": "MIT", "license": "MIT",
"author": "Aman Varshney", "author": "Aman Varshney",
"bin": { "bin": {
"create-better-t-stack": "dist/cli.js" "create-better-t-stack": "dist/cli.js"
}, },
"files": [ "files": [
"templates", "templates",
"dist" "dist"
], ],
"keywords": [ "keywords": [
"better-t-stack", "better-t-stack",
"typescript", "typescript",
"boilerplate", "boilerplate",
"starter", "starter",
"cli", "cli",
"turborepo", "turborepo",
"trpc", "trpc",
"better-auth", "better-auth",
"monorepo", "monorepo",
"fullstack", "fullstack",
"type-safety", "type-safety",
"react", "react",
"react-native", "react-native",
"expo", "expo",
"hono", "hono",
"elysia", "elysia",
"drizzle", "drizzle",
"prisma", "prisma",
"tanstack", "tanstack",
"tailwind", "tailwind",
"shadcn", "shadcn",
"pwa", "pwa",
"tauri", "tauri",
"biome" "biome"
], ],
"repository": { "repository": {
"type": "git", "type": "git",
"url": "git+https://github.com/AmanVarshney01/create-better-t-stack.git", "url": "git+https://github.com/AmanVarshney01/create-better-t-stack.git",
"directory": "apps/cli" "directory": "apps/cli"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"
}, },
"homepage": "https://better-t-stack.dev/", "homepage": "https://better-t-stack.dev/",
"scripts": { "scripts": {
"build": "tsdown", "build": "tsdown",
"dev": "tsdown --watch", "dev": "tsdown --watch",
"check-types": "tsc --noEmit", "check-types": "tsc --noEmit",
"check": "biome check --write .", "check": "biome check --write .",
"test": "bun run build && vitest run", "test": "bun run build && vitest run",
"test:ui": "bun run build && vitest --ui", "test:ui": "bun run build && vitest --ui",
"test:with-build": "bun run build && WITH_BUILD=1 vitest --ui", "test:with-build": "bun run build && WITH_BUILD=1 vitest --ui",
"prepublishOnly": "npm run build" "prepublishOnly": "npm run build"
}, },
"exports": { "exports": {
".": { ".": {
"types": "./dist/index.d.ts", "types": "./dist/index.d.ts",
"import": "./dist/index.js" "import": "./dist/index.js"
} }
}, },
"dependencies": { "dependencies": {
"@biomejs/js-api": "^3.0.0", "@biomejs/js-api": "^3.0.0",
"@biomejs/wasm-nodejs": "^2.2.4", "@biomejs/wasm-nodejs": "^2.2.4",
"@clack/prompts": "^1.0.0-alpha.4", "@clack/prompts": "^1.0.0-alpha.4",
"consola": "^3.4.2", "consola": "^3.4.2",
"execa": "^9.6.0", "execa": "^9.6.0",
"fs-extra": "^11.3.1", "fs-extra": "^11.3.1",
"gradient-string": "^3.0.0", "gradient-string": "^3.0.0",
"handlebars": "^4.7.8", "handlebars": "^4.7.8",
"jsonc-parser": "^3.3.1", "jsonc-parser": "^3.3.1",
"picocolors": "^1.1.1", "picocolors": "^1.1.1",
"tinyglobby": "^0.2.15", "tinyglobby": "^0.2.15",
"trpc-cli": "^0.10.2", "trpc-cli": "^0.10.2",
"ts-morph": "^27.0.0", "ts-morph": "^27.0.0",
"zod": "^4.1.5" "zod": "^4.1.5"
}, },
"devDependencies": { "devDependencies": {
"@types/fs-extra": "^11.0.4", "@types/fs-extra": "^11.0.4",
"@types/node": "^24.3.1", "@types/node": "^24.3.1",
"@vitest/ui": "^3.2.4", "@vitest/ui": "^3.2.4",
"tsdown": "^0.14.2", "tsdown": "^0.14.2",
"typescript": "^5.9.2", "typescript": "^5.9.2",
"vitest": "^3.2.4" "vitest": "^3.2.4"
} }
} }

View File

@@ -851,7 +851,7 @@ export async function handleExtras(projectDir: string, context: ProjectConfig) {
const pnpmWorkspaceSrc = path.join(extrasDir, "pnpm-workspace.yaml"); const pnpmWorkspaceSrc = path.join(extrasDir, "pnpm-workspace.yaml");
const pnpmWorkspaceDest = path.join(projectDir, "pnpm-workspace.yaml"); const pnpmWorkspaceDest = path.join(projectDir, "pnpm-workspace.yaml");
if (await fs.pathExists(pnpmWorkspaceSrc)) { if (await fs.pathExists(pnpmWorkspaceSrc)) {
await fs.copy(pnpmWorkspaceSrc, pnpmWorkspaceDest); await processTemplate(pnpmWorkspaceSrc, pnpmWorkspaceDest, context);
} }
} }

View File

@@ -5,6 +5,15 @@ import handlebars from "handlebars";
import type { ProjectConfig } from "../types"; import type { ProjectConfig } from "../types";
import { formatFileWithBiome } from "./biome-formatter"; import { formatFileWithBiome } from "./biome-formatter";
const BINARY_EXTENSIONS = new Set([
".png", ".ico", ".svg",
]);
function isBinaryFile(filePath: string): boolean {
const ext = path.extname(filePath).toLowerCase();
return BINARY_EXTENSIONS.has(ext);
}
export async function processTemplate( export async function processTemplate(
srcPath: string, srcPath: string,
destPath: string, destPath: string,
@@ -13,6 +22,11 @@ export async function processTemplate(
try { try {
await fs.ensureDir(path.dirname(destPath)); await fs.ensureDir(path.dirname(destPath));
if (isBinaryFile(srcPath) && !srcPath.endsWith(".hbs")) {
await fs.copy(srcPath, destPath);
return;
}
let content: string; let content: string;
if (srcPath.endsWith(".hbs")) { if (srcPath.endsWith(".hbs")) {

View File

@@ -1,12 +1,13 @@
import { createAuthClient } from "better-auth/react";
import { expoClient } from "@better-auth/expo/client"; import { expoClient } from "@better-auth/expo/client";
import { createAuthClient } from "better-auth/react";
import * as SecureStore from "expo-secure-store"; import * as SecureStore from "expo-secure-store";
export const authClient = createAuthClient({ export const authClient = createAuthClient({
baseURL: process.env.EXPO_PUBLIC_SERVER_URL, baseURL: process.env.EXPO_PUBLIC_SERVER_URL,
plugins: [ plugins: [
expoClient({ expoClient({
storagePrefix: "my-better-t-app", scheme: "mybettertapp",
storagePrefix: "{{projectName}}",
storage: SecureStore, storage: SecureStore,
}), }),
], ],

View File

@@ -16,7 +16,7 @@ export const auth = betterAuth({
trustedOrigins: [ trustedOrigins: [
process.env.CORS_ORIGIN || "", process.env.CORS_ORIGIN || "",
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}} {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
"my-better-t-app://", "mybettertapp://", "exp://"
{{/if}} {{/if}}
], ],
emailAndPassword: { emailAndPassword: {
@@ -55,7 +55,7 @@ export const auth = betterAuth({
trustedOrigins: [ trustedOrigins: [
process.env.CORS_ORIGIN || "", process.env.CORS_ORIGIN || "",
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}} {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
"my-better-t-app://", "mybettertapp://", "exp://"
{{/if}} {{/if}}
], ],
emailAndPassword: { emailAndPassword: {
@@ -124,7 +124,7 @@ export const auth = betterAuth({
trustedOrigins: [ trustedOrigins: [
process.env.CORS_ORIGIN || "", process.env.CORS_ORIGIN || "",
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}} {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
"my-better-t-app://", "mybettertapp://", "exp://"
{{/if}} {{/if}}
], ],
emailAndPassword: { emailAndPassword: {
@@ -154,7 +154,7 @@ export const auth = betterAuth({
trustedOrigins: [ trustedOrigins: [
process.env.CORS_ORIGIN || "", process.env.CORS_ORIGIN || "",
{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}} {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}}
"my-better-t-app://", "mybettertapp://", "exp://"
{{/if}} {{/if}}
], ],
emailAndPassword: { emailAndPassword: {
@@ -171,4 +171,4 @@ export const auth = betterAuth({
, plugins: [expo()] , plugins: [expo()]
{{/if}} {{/if}}
}); });
{{/if}} {{/if}}

View File

@@ -9,8 +9,8 @@
}, },
"dependencies": { "dependencies": {
"next": "15.5.0", "next": "15.5.0",
"react": "^19.0.0", "react": "19.1.0",
"react-dom": "^19.0.0", "react-dom": "19.1.0",
"dotenv": "^17.2.1" "dotenv": "^17.2.1"
}, },
{{#if (eq dbSetup 'supabase')}} {{#if (eq dbSetup 'supabase')}}
@@ -20,7 +20,7 @@
{{/if}} {{/if}}
"devDependencies": { "devDependencies": {
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "~19.1.10",
"zod": "^4.0.13", "zod": "^4.0.13",
"typescript": "^5" "typescript": "^5"
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 77 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 384 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@@ -1,2 +0,0 @@
// @ts-ignore
/// <reference types="nativewind/types" />

View File

@@ -1,46 +0,0 @@
{
"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
}
}
}

View File

@@ -0,0 +1,49 @@
{
"expo": {
"name": "{{projectName}}",
"slug": "{{projectName}}",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "mybettertapp",
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"backgroundColor": "#E6F4FE",
"foregroundImage": "./assets/images/android-icon-foreground.png",
"backgroundImage": "./assets/images/android-icon-background.png",
"monochromeImage": "./assets/images/android-icon-monochrome.png"
},
"edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false,
"package": "com.anonymous.mybettertapp"
},
"web": {
"output": "static",
"favicon": "./assets/images/favicon.png"
},
"plugins": [
"expo-router",
[
"expo-splash-screen",
{
"image": "./assets/images/splash-icon.png",
"imageWidth": 200,
"resizeMode": "contain",
"backgroundColor": "#ffffff",
"dark": {
"backgroundColor": "#000000"
}
}
]
],
"experiments": {
"typedRoutes": true,
"reactCompiler": true
}
}
}

View File

@@ -7,7 +7,7 @@ export default function TabLayout() {
return ( return (
<Tabs <Tabs
screenOptions={{ screenOptions=\{{
headerShown: false, headerShown: false,
tabBarActiveTintColor: isDarkColorScheme tabBarActiveTintColor: isDarkColorScheme
? "hsl(217.2 91.2% 59.8%)" ? "hsl(217.2 91.2% 59.8%)"
@@ -27,14 +27,14 @@ export default function TabLayout() {
> >
<Tabs.Screen <Tabs.Screen
name="index" name="index"
options={{ options=\{{
title: "Home", title: "Home",
tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />, tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />,
}} }}
/> />
<Tabs.Screen <Tabs.Screen
name="two" name="two"
options={{ options=\{{
title: "Explore", title: "Explore",
tabBarIcon: ({ color }) => ( tabBarIcon: ({ color }) => (
<TabBarIcon name="compass" color={color} /> <TabBarIcon name="compass" color={color} />

View File

@@ -1,47 +0,0 @@
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;
}
}`;

View File

@@ -5,7 +5,7 @@ import { Text, View } from "react-native";
export default function NotFoundScreen() { export default function NotFoundScreen() {
return ( return (
<> <>
<Stack.Screen options={{ title: "Oops!" }} /> <Stack.Screen options=\{{ title: "Oops!" }} />
<Container> <Container>
<View className="flex-1 justify-center items-center p-6"> <View className="flex-1 justify-center items-center p-6">
<View className="items-center"> <View className="items-center">

View File

@@ -1,11 +0,0 @@
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,
};
};

View File

@@ -0,0 +1,14 @@
module.exports = (api) => {
api.cache(true);
const plugins = [];
plugins.push("react-native-worklets/plugin");
return {
presets: [
["babel-preset-expo", { jsxImportSource: "nativewind" }],
"nativewind/babel",
],
plugins,
};
};

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { SafeAreaView } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context";
export const Container = ({ children }: { children: React.ReactNode }) => { export const Container = ({ children }: { children: React.ReactNode }) => {
return ( return (

View File

@@ -16,7 +16,7 @@ export const HeaderButton = forwardRef<
name="info-circle" name="info-circle"
size={20} size={20}
className="text-secondary-foreground" className="text-secondary-foreground"
style={{ style=\{{
opacity: pressed ? 0.7 : 1, opacity: pressed ? 0.7 : 1,
}} }}
/> />

View File

@@ -4,5 +4,5 @@ export const TabBarIcon = (props: {
name: React.ComponentProps<typeof FontAwesome>["name"]; name: React.ComponentProps<typeof FontAwesome>["name"];
color: string; color: string;
}) => { }) => {
return <FontAwesome size={24} style={{ marginBottom: -3 }} {...props} />; return <FontAwesome size={24} style=\{{ marginBottom: -3 }} {...props} />;
}; };

View File

@@ -10,39 +10,41 @@
"web": "expo start --web" "web": "expo start --web"
}, },
"dependencies": { "dependencies": {
"@expo/vector-icons": "^14.0.4", "@expo/vector-icons": "^15.0.2",
"@react-navigation/bottom-tabs": "^7.2.0", "@react-navigation/bottom-tabs": "^7.2.0",
"@react-navigation/drawer": "^7.1.1", "@react-navigation/drawer": "^7.1.1",
"@react-navigation/native": "^7.0.14", "@react-navigation/native": "^7.0.14",
"@tanstack/react-form": "^1.0.5", "@tanstack/react-form": "^1.0.5",
"@tanstack/react-query": "^5.69.2", "@tanstack/react-query": "^5.85.5",
{{#if (includes examples "ai")}} {{#if (includes examples "ai")}}
"@stardazed/streams-text-encoding": "^1.0.2", "@stardazed/streams-text-encoding": "^1.0.2",
"@ungap/structured-clone": "^1.3.0", "@ungap/structured-clone": "^1.3.0",
{{/if}} {{/if}}
"expo": "^53.0.4", "expo": "^54.0.1",
"expo-constants": "~17.1.4", "expo-constants": "~18.0.8",
"expo-crypto": "~14.1.5", "expo-crypto": "~15.0.6",
"expo-linking": "~7.1.4", "expo-linking": "~8.0.7",
"expo-navigation-bar": "~4.2.3", "expo-navigation-bar": "~5.0.8",
"expo-router": "~5.0.3", "expo-router": "~6.0.0",
"expo-secure-store": "~14.2.3", "expo-secure-store": "~15.0.6",
"expo-status-bar": "~2.2.3", "expo-splash-screen": "~31.0.8",
"expo-system-ui": "~5.0.6", "expo-status-bar": "~3.0.7",
"expo-web-browser": "~14.1.6", "expo-system-ui": "~6.0.7",
"expo-web-browser": "~15.0.6",
"nativewind": "^4.1.23", "nativewind": "^4.1.23",
"react": "19.0.0", "react": "19.1.0",
"react-dom": "19.0.0", "react-dom": "19.1.0",
"react-native": "0.79.1", "react-native": "0.81.4",
"react-native-gesture-handler": "~2.24.0", "react-native-gesture-handler": "~2.28.0",
"react-native-reanimated": "~3.17.4", "react-native-reanimated": "~4.1.0",
"react-native-safe-area-context": "5.3.0", "react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.10.0", "react-native-screens": "~4.16.0",
"react-native-web": "^0.20.0" "react-native-web": "^0.21.0",
"react-native-worklets": "^0.5.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.26.10", "@babel/core": "^7.26.10",
"@types/react": "~19.0.10", "@types/react": "~19.1.10",
"tailwindcss": "^3.4.17", "tailwindcss": "^3.4.17",
"typescript": "~5.8.2" "typescript": "~5.8.2"
}, },

View File

@@ -1,59 +0,0 @@
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: [],
};

View File

@@ -0,0 +1,59 @@
import { hairlineWidth } from "nativewind/theme";
/** @type {import('tailwindcss').Config} */
export const darkMode = "class";
export const content = [
"./app/**/*.{js,ts,tsx}",
"./components/**/*.{js,ts,tsx}",
];
export const presets = [require("nativewind/preset")];
export const 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(),
},
},
};
export const plugins = [];

View File

@@ -1,44 +0,0 @@
{
"expo": {
"name": "my-better-t-app",
"slug": "my-better-t-app",
"version": "1.0.0",
"newArchEnabled": true,
"scheme": "my-better-t-app",
"web": {
"bundler": "metro",
"output": "static",
"favicon": "./assets/favicon.png"
},
"plugins": [
"expo-router",
"react-native-edge-to-edge",
"expo-secure-store"
],
"experiments": {
"typedRoutes": true,
"tsconfigPaths": true,
"reactCompiler": true
},
"orientation": "portrait",
"icon": "./assets/icon.png",
"userInterfaceStyle": "automatic",
"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"
}
}
}

View File

@@ -0,0 +1,49 @@
{
"expo": {
"name": "{{projectName}}",
"slug": "{{projectName}}",
"version": "1.0.0",
"orientation": "portrait",
"icon": "./assets/images/icon.png",
"scheme": "mybettertapp",
"userInterfaceStyle": "automatic",
"newArchEnabled": true,
"ios": {
"supportsTablet": true
},
"android": {
"adaptiveIcon": {
"backgroundColor": "#E6F4FE",
"foregroundImage": "./assets/images/android-icon-foreground.png",
"backgroundImage": "./assets/images/android-icon-background.png",
"monochromeImage": "./assets/images/android-icon-monochrome.png"
},
"edgeToEdgeEnabled": true,
"predictiveBackGestureEnabled": false,
"package": "com.anonymous.mybettertapp6"
},
"web": {
"output": "static",
"favicon": "./assets/images/favicon.png"
},
"plugins": [
"expo-router",
[
"expo-splash-screen",
{
"image": "./assets/images/splash-icon.png",
"imageWidth": 200,
"resizeMode": "contain",
"backgroundColor": "#ffffff",
"dark": {
"backgroundColor": "#000000"
}
}
]
],
"experiments": {
"typedRoutes": true,
"reactCompiler": true
}
}
}

View File

@@ -8,7 +8,7 @@ export default function TabLayout() {
return ( return (
<Tabs <Tabs
screenOptions={{ screenOptions=\{{
headerShown: false, headerShown: false,
tabBarActiveTintColor: theme.colors.primary, tabBarActiveTintColor: theme.colors.primary,
tabBarInactiveTintColor: theme.colors.mutedForeground, tabBarInactiveTintColor: theme.colors.mutedForeground,
@@ -20,14 +20,14 @@ export default function TabLayout() {
> >
<Tabs.Screen <Tabs.Screen
name="index" name="index"
options={{ options=\{{
title: "Home", title: "Home",
tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />, tabBarIcon: ({ color }) => <TabBarIcon name="home" color={color} />,
}} }}
/> />
<Tabs.Screen <Tabs.Screen
name="two" name="two"
options={{ options=\{{
title: "Explore", title: "Explore",
tabBarIcon: ({ color }) => ( tabBarIcon: ({ color }) => (
<TabBarIcon name="compass" color={color} /> <TabBarIcon name="compass" color={color} />

View File

@@ -46,9 +46,7 @@ export default function Home() {
contentContainerStyle={styles.container} contentContainerStyle={styles.container}
showsVerticalScrollIndicator={false} showsVerticalScrollIndicator={false}
> >
<Text className="font-mono text-foreground text-3xl font-bold mb-4"> <Text style={styles.heroTitle}>BETTER T STACK</Text>
BETTER T STACK
</Text>
<View style={styles.statusCard}> <View style={styles.statusCard}>
<View style={styles.statusHeader}> <View style={styles.statusHeader}>
<Text style={styles.statusTitle}>System Status</Text> <Text style={styles.statusTitle}>System Status</Text>

View File

@@ -1,48 +0,0 @@
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;
}
}`;

View File

@@ -1,12 +1,12 @@
import { Container } from "@/components/container";
import { Link, Stack } from "expo-router"; import { Link, Stack } from "expo-router";
import { Text, View } from "react-native"; import { Text, View } from "react-native";
import { StyleSheet } from "react-native-unistyles"; import { StyleSheet } from "react-native-unistyles";
import { Container } from "@/components/container";
export default function NotFoundScreen() { export default function NotFoundScreen() {
return ( return (
<> <>
<Stack.Screen options={{ title: "Oops!" }} /> <Stack.Screen options=\{{ title: "Oops!" }} />
<Container> <Container>
<View style={styles.container}> <View style={styles.container}>
<View style={styles.content}> <View style={styles.content}>

View File

@@ -22,14 +22,14 @@ import { QueryClientProvider } from "@tanstack/react-query";
import { Stack } from "expo-router"; import { Stack } from "expo-router";
import { GestureHandlerRootView } from "react-native-gesture-handler"; import { GestureHandlerRootView } from "react-native-gesture-handler";
import { useUnistyles } from "react-native-unistyles"; import { useUnistyles } from "react-native-unistyles";
import { StatusBar } from "expo-status-bar";
export const unstable_settings = { export const unstable_settings = {
// Ensure that reloading on `/modal` keeps a back button present.
initialRouteName: "(drawer)", initialRouteName: "(drawer)",
}; };
{{#if (eq backend "convex")}} {{#if (eq backend "convex")}}
const convex = new ConvexReactClient(process.env.EXPO_PUBLIC_CONVEX_URL!, { const convex = new ConvexReactClient(process.env.EXPO_PUBLIC_CONVEX_URL || "", {
unsavedChangesWarning: false, unsavedChangesWarning: false,
}); });
{{/if}} {{/if}}

View File

@@ -1,21 +0,0 @@
module.exports = function (api) {
api.cache(true);
const plugins = [];
plugins.push([
'react-native-unistyles/plugin',
{
root: "src",
autoProcessRoot: 'app',
autoProcessImports: ['@/components']
},
]);
plugins.push('react-native-reanimated/plugin');
return {
presets: ['babel-preset-expo'],
plugins,
};
};

View File

@@ -0,0 +1,21 @@
module.exports = (api) => {
api.cache(true);
const plugins = [];
plugins.push([
"react-native-unistyles/plugin",
{
root: "src",
autoProcessRoot: "app",
autoProcessImports: ["@/components"],
},
]);
plugins.push("react-native-worklets/plugin");
return {
presets: ["babel-preset-expo"],
plugins,
};
};

View File

@@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { SafeAreaView } from "react-native"; import { SafeAreaView } from "react-native-safe-area-context";
import { StyleSheet } from "react-native-unistyles"; import { StyleSheet } from "react-native-unistyles";
export const Container = ({ children }: { children: React.ReactNode }) => { export const Container = ({ children }: { children: React.ReactNode }) => {

View File

@@ -14,7 +14,7 @@ export const HeaderButton = forwardRef<
name="info-circle" name="info-circle"
size={20} size={20}
color={styles.icon.color} color={styles.icon.color}
style={{ style=\{{
opacity: pressed ? 0.7 : 1, opacity: pressed ? 0.7 : 1,
}} }}
/> />

View File

@@ -4,5 +4,5 @@ export const TabBarIcon = (props: {
name: React.ComponentProps<typeof FontAwesome>["name"]; name: React.ComponentProps<typeof FontAwesome>["name"];
color: string; color: string;
}) => { }) => {
return <FontAwesome size={24} style={{ marginBottom: -3 }} {...props} />; return <FontAwesome size={24} style=\{{ marginBottom: -3 }} {...props} />;
}; };

View File

@@ -1,3 +0,0 @@
/// <reference types="expo/types" />
// NOTE: This file should not be edited and should be in your git ignore

View File

@@ -1,20 +0,0 @@
// Learn more https://docs.expo.io/guides/customizing-metro
const { getDefaultConfig } = require("expo/metro-config");
const path = require("path");
const workspaceRoot = path.resolve(__dirname, "../..");
const projectRoot = __dirname;
const config = getDefaultConfig(projectRoot);
// 1. Watch all files within the monorepo
config.watchFolders = [workspaceRoot];
// 2. Let Metro know where to resolve packages, and in what order
config.resolver.nodeModulesPaths = [
path.resolve(projectRoot, "node_modules"),
path.resolve(workspaceRoot, "node_modules"),
];
// 3. Force Metro to resolve (sub)dependencies only from the `nodeModulesPaths`
config.resolver.disableHierarchicalLookup = true;
module.exports = config;

View File

@@ -0,0 +1,5 @@
const { getDefaultConfig } = require("expo/metro-config");
const config = getDefaultConfig(__dirname);
module.exports = config;

View File

@@ -1,6 +1,7 @@
{ {
"name": "native", "name": "native",
"version": "1.0.0", "version": "1.0.0",
"private": true,
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"dev": "expo start --clear", "dev": "expo start --clear",
@@ -9,7 +10,7 @@
"web": "expo start --web" "web": "expo start --web"
}, },
"dependencies": { "dependencies": {
"@expo/vector-icons": "^14.1.0", "@expo/vector-icons": "^15.0.2",
"@react-navigation/bottom-tabs": "^7.3.10", "@react-navigation/bottom-tabs": "^7.3.10",
"@react-navigation/drawer": "^7.3.9", "@react-navigation/drawer": "^7.3.9",
"@react-navigation/native": "^7.1.6", "@react-navigation/native": "^7.1.6",
@@ -17,35 +18,35 @@
"@stardazed/streams-text-encoding": "^1.0.2", "@stardazed/streams-text-encoding": "^1.0.2",
"@ungap/structured-clone": "^1.3.0", "@ungap/structured-clone": "^1.3.0",
{{/if}} {{/if}}
"@tanstack/react-form": "^1.14.0", "@tanstack/react-form": "^1.0.5",
"babel-plugin-react-compiler": "^19.1.0-rc.2", "expo": "^54.0.0",
"expo": "^53.0.17", "expo-constants": "~18.0.8",
"expo-constants": "~17.1.7", "expo-crypto": "~15.0.6",
"expo-crypto": "~14.1.5", "expo-linking": "~8.0.7",
"expo-linking": "~7.1.7", "expo-router": "~6.0.0",
"expo-router": "~5.1.3", "expo-secure-store": "~15.0.6",
"expo-secure-store": "~14.2.3", "expo-splash-screen": "~31.0.8",
"expo-status-bar": "~2.2.3", "expo-status-bar": "^3.0.7",
"expo-system-ui": "~5.0.10", "expo-system-ui": "~6.0.7",
"expo-dev-client": "~5.2.4", "expo-dev-client": "~6.0.11",
"expo-web-browser": "~14.2.0", "expo-web-browser": "~15.0.6",
"react": "19.0.0", "react": "19.1.0",
"react-dom": "19.0.0", "react-dom": "19.1.0",
"react-native": "0.79.5", "react-native": "0.81.4",
"react-native-edge-to-edge": "1.6.0", "react-native-edge-to-edge": "^1.7.0",
"react-native-gesture-handler": "~2.24.0", "react-native-gesture-handler": "~2.28.0",
"react-native-nitro-modules": "0.26.3", "react-native-nitro-modules": "^0.29.4",
"react-native-reanimated": "~3.17.4", "react-native-reanimated": "~4.1.0",
"react-native-safe-area-context": "5.4.0", "react-native-safe-area-context": "~5.6.0",
"react-native-screens": "~4.11.1", "react-native-screens": "~4.16.0",
"react-native-unistyles": "^3.0.0", "react-native-unistyles": "^3.0.12",
"react-native-web": "^0.20.0" "react-native-web": "^0.21.0",
"react-native-worklets": "^0.5.1"
}, },
"devDependencies": { "devDependencies": {
"ajv": "^8.17.1", "ajv": "^8.17.1",
"@babel/core": "^7.28.0", "@babel/core": "^7.28.0",
"@types/react": "~19.0.10", "@types/react": "~19.1.10",
"typescript": "~5.8.3" "typescript": "~5.9.2"
}, }
"private": true
} }

View File

@@ -1,98 +0,0 @@
const sharedColors = {
success: "#22C55E",
destructive: "#EF4444",
warning: "#F59E0B",
info: "#3B82F6",
} as const;
export const lightTheme = {
colors: {
...sharedColors,
typography: "hsl(222.2 84% 4.9%)",
background: "hsl(0 0% 100%)",
foreground: "hsl(222.2 84% 4.9%)",
card: "hsl(0 0% 100%)",
cardForeground: "hsl(222.2 84% 4.9%)",
primary: "hsl(221.2 83.2% 53.3%)",
primaryForeground: "hsl(210 40% 98%)",
secondary: "hsl(210 40% 96%)",
secondaryForeground: "hsl(222.2 84% 4.9%)",
muted: "hsl(210 40% 96%)",
mutedForeground: "hsl(215.4 16.3% 46.9%)",
accent: "hsl(210 40% 96%)",
accentForeground: "hsl(222.2 84% 4.9%)",
border: "hsl(214.3 31.8% 91.4%)",
input: "hsl(214.3 31.8% 91.4%)",
ring: "hsl(221.2 83.2% 53.3%)",
},
spacing: {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
xxl: 48,
},
borderRadius: {
sm: 6,
md: 8,
lg: 12,
xl: 16,
},
fontSize: {
xs: 12,
sm: 14,
base: 16,
lg: 18,
xl: 20,
"2xl": 24,
"3xl": 30,
"4xl": 36,
},
} as const;
export const darkTheme = {
colors: {
...sharedColors,
typography: "hsl(210 40% 98%)",
background: "hsl(222.2 84% 4.9%)",
foreground: "hsl(210 40% 98%)",
card: "hsl(222.2 84% 4.9%)",
cardForeground: "hsl(210 40% 98%)",
primary: "hsl(217.2 91.2% 59.8%)",
primaryForeground: "hsl(222.2 84% 4.9%)",
secondary: "hsl(217.2 32.6% 17.5%)",
secondaryForeground: "hsl(210 40% 98%)",
muted: "hsl(217.2 32.6% 17.5%)",
mutedForeground: "hsl(215 20.2% 65.1%)",
accent: "hsl(217.2 32.6% 17.5%)",
accentForeground: "hsl(210 40% 98%)",
border: "hsl(217.2 32.6% 17.5%)",
input: "hsl(217.2 32.6% 17.5%)",
ring: "hsl(224.3 76.3% 94.1%)",
},
spacing: {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
xxl: 48,
},
borderRadius: {
sm: 6,
md: 8,
lg: 12,
xl: 16,
},
fontSize: {
xs: 12,
sm: 14,
base: 16,
lg: 18,
xl: 20,
"2xl": 24,
"3xl": 30,
"4xl": 36,
},
} as const;

View File

@@ -0,0 +1,98 @@
const sharedColors = {
success: "#22C55E",
destructive: "#EF4444",
warning: "#F59E0B",
info: "#3B82F6",
} as const;
export const lightTheme = {
colors: {
...sharedColors,
typography: "hsl(0 0% 0%)",
background: "hsl(0 0% 100%)",
foreground: "hsl(0 0% 0%)",
card: "hsl(0 0% 98%)",
cardForeground: "hsl(0 0% 0%)",
primary: "hsl(0 0% 10%)",
primaryForeground: "hsl(0 0% 100%)",
secondary: "hsl(0 0% 95%)",
secondaryForeground: "hsl(0 0% 0%)",
muted: "hsl(0 0% 96%)",
mutedForeground: "hsl(0 0% 45%)",
accent: "hsl(0 0% 96%)",
accentForeground: "hsl(0 0% 0%)",
border: "hsl(0 0% 90%)",
input: "hsl(0 0% 90%)",
ring: "hsl(0 0% 20%)",
},
spacing: {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
xxl: 48,
},
borderRadius: {
sm: 6,
md: 8,
lg: 12,
xl: 16,
},
fontSize: {
xs: 12,
sm: 14,
base: 16,
lg: 18,
xl: 20,
"2xl": 24,
"3xl": 30,
"4xl": 36,
},
} as const;
export const darkTheme = {
colors: {
...sharedColors,
typography: "hsl(0 0% 100%)",
background: "hsl(0 0% 0%)",
foreground: "hsl(0 0% 100%)",
card: "hsl(0 0% 2%)",
cardForeground: "hsl(0 0% 100%)",
primary: "hsl(0 0% 90%)",
primaryForeground: "hsl(0 0% 0%)",
secondary: "hsl(0 0% 10%)",
secondaryForeground: "hsl(0 0% 100%)",
muted: "hsl(0 0% 8%)",
mutedForeground: "hsl(0 0% 65%)",
accent: "hsl(0 0% 8%)",
accentForeground: "hsl(0 0% 100%)",
border: "hsl(0 0% 15%)",
input: "hsl(0 0% 15%)",
ring: "hsl(0 0% 80%)",
},
spacing: {
xs: 4,
sm: 8,
md: 16,
lg: 24,
xl: 32,
xxl: 48,
},
borderRadius: {
sm: 6,
md: 8,
lg: 12,
xl: 16,
},
fontSize: {
xs: 12,
sm: 14,
base: 16,
lg: 18,
xl: 20,
"2xl": 24,
"3xl": 30,
"4xl": 36,
},
} as const;

View File

@@ -15,8 +15,8 @@
"lucide-react": "^0.487.0", "lucide-react": "^0.487.0",
"next": "15.5.0", "next": "15.5.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "^19.0.0", "react": "19.1.0",
"react-dom": "^19.0.0", "react-dom": "19.1.0",
"sonner": "^2.0.5", "sonner": "^2.0.5",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"tw-animate-css": "^1.3.4", "tw-animate-css": "^1.3.4",
@@ -25,7 +25,7 @@
"devDependencies": { "devDependencies": {
"@tailwindcss/postcss": "^4.1.10", "@tailwindcss/postcss": "^4.1.10",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19", "@types/react": "~19.1.10",
"@types/react-dom": "^19", "@types/react-dom": "^19",
"tailwindcss": "^4.1.10", "tailwindcss": "^4.1.10",
"typescript": "^5" "typescript": "^5"

View File

@@ -19,8 +19,8 @@
"isbot": "^5.1.28", "isbot": "^5.1.28",
"lucide-react": "^0.511.0", "lucide-react": "^0.511.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "19.0.0", "react": "19.1.0",
"react-dom": "19.0.0", "react-dom": "19.1.0",
"react-router": "^7.6.1", "react-router": "^7.6.1",
"sonner": "^2.0.3", "sonner": "^2.0.3",
"tailwind-merge": "^3.3.0", "tailwind-merge": "^3.3.0",
@@ -31,7 +31,7 @@
"@react-router/dev": "^7.6.1", "@react-router/dev": "^7.6.1",
"@tailwindcss/vite": "^4.1.8", "@tailwindcss/vite": "^4.1.8",
"@types/node": "^20", "@types/node": "^20",
"@types/react": "^19.0.12", "@types/react": "~19.1.10",
"@types/react-dom": "^19.0.4", "@types/react-dom": "^19.0.4",
"react-router-devtools": "^1.1.0", "react-router-devtools": "^1.1.0",
"tailwindcss": "^4.1.8", "tailwindcss": "^4.1.8",

View File

@@ -20,8 +20,8 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lucide-react": "^0.473.0", "lucide-react": "^0.473.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "^19.0.0", "react": "19.1.0",
"react-dom": "^19.0.0", "react-dom": "19.1.0",
"sonner": "^2.0.5", "sonner": "^2.0.5",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
"tw-animate-css": "^1.2.5", "tw-animate-css": "^1.2.5",
@@ -31,7 +31,7 @@
"@tanstack/react-router-devtools": "^1.114.27", "@tanstack/react-router-devtools": "^1.114.27",
"@tanstack/router-plugin": "^1.114.27", "@tanstack/router-plugin": "^1.114.27",
"@types/node": "^22.13.13", "@types/node": "^22.13.13",
"@types/react": "^19.0.12", "@types/react": "~19.1.10",
"@types/react-dom": "^19.0.4", "@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^4.3.4", "@vitejs/plugin-react": "^4.3.4",
"postcss": "^8.5.3", "postcss": "^8.5.3",

View File

@@ -20,8 +20,8 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lucide-react": "^0.525.0", "lucide-react": "^0.525.0",
"next-themes": "^0.4.6", "next-themes": "^0.4.6",
"react": "19.0.0", "react": "19.1.0",
"react-dom": "19.0.0", "react-dom": "19.1.0",
"sonner": "^2.0.3", "sonner": "^2.0.3",
"tailwindcss": "^4.1.3", "tailwindcss": "^4.1.3",
"tailwind-merge": "^3.3.1", "tailwind-merge": "^3.3.1",
@@ -33,7 +33,7 @@
"@tanstack/react-router-devtools": "^1.121.0-alpha.27", "@tanstack/react-router-devtools": "^1.121.0-alpha.27",
"@testing-library/dom": "^10.4.0", "@testing-library/dom": "^10.4.0",
"@testing-library/react": "^16.2.0", "@testing-library/react": "^16.2.0",
"@types/react": "^19.0.12", "@types/react": "~19.1.10",
"@types/react-dom": "^19.0.4", "@types/react-dom": "^19.0.4",
"@vitejs/plugin-react": "^5.0.1", "@vitejs/plugin-react": "^5.0.1",
"jsdom": "^26.0.0", "jsdom": "^26.0.0",