From d79092a23f00f313b2cca31aa1d777d9b161cb5d Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Sat, 8 Nov 2025 22:01:38 +0530 Subject: [PATCH 1/5] feat(cli): add uniwind with heroui-native --- apps/cli/README.md | 4 +- apps/cli/src/helpers/addons/examples-setup.ts | 3 +- .../cli/src/helpers/addons/ultracite-setup.ts | 3 +- apps/cli/src/helpers/core/api-setup.ts | 11 +- apps/cli/src/helpers/core/auth-setup.ts | 16 +- apps/cli/src/helpers/core/create-readme.ts | 9 +- apps/cli/src/helpers/core/env-setup.ts | 6 +- .../cli/src/helpers/core/post-installation.ts | 6 +- apps/cli/src/helpers/core/template-manager.ts | 46 +-- apps/cli/src/prompts/auth.ts | 6 +- apps/cli/src/prompts/frontend.ts | 11 +- apps/cli/src/types.ts | 3 +- .../cli/src/utils/better-auth-plugin-setup.ts | 3 +- apps/cli/src/utils/compatibility-rules.ts | 6 +- apps/cli/src/utils/config-validation.ts | 3 +- apps/cli/src/utils/setup-catalogs.ts | 2 +- .../convex/backend/convex/auth.ts.hbs | 10 +- .../convex/backend/convex/http.ts.hbs | 2 +- .../nativewind/components/sign-in.tsx.hbs | 86 ----- .../nativewind/components/sign-up.tsx.hbs | 97 ------ .../native/uniwind/components/sign-in.tsx.hbs | 91 ++++++ .../native/uniwind/components/sign-up.tsx.hbs | 102 ++++++ .../nativewind/app/(drawer)/index.tsx.hbs | 95 ------ .../nativewind/components/sign-in.tsx.hbs | 93 ------ .../nativewind/components/sign-up.tsx.hbs | 104 ------ .../native/uniwind/app/(drawer)/index.tsx.hbs | 123 ++++++++ .../native/uniwind/components/sign-in.tsx.hbs | 99 ++++++ .../native/uniwind/components/sign-up.tsx.hbs | 130 ++++++++ .../better-auth/server/base/src/index.ts.hbs | 10 +- .../app/(drawer)/ai.tsx.hbs | 103 +++--- .../{nativewind => uniwind}/polyfills.js | 1 + .../nativewind/app/(drawer)/todos.tsx.hbs | 295 ------------------ .../native/uniwind/app/(drawer)/todos.tsx.hbs | 295 ++++++++++++++++++ apps/cli/templates/extras/bunfig.toml.hbs | 6 +- .../templates/frontend/native/bare/_gitignore | 18 ++ .../native/{nativewind => bare}/app.json.hbs | 1 + .../app/(drawer)/(tabs)/_layout.tsx.hbs | 31 +- .../bare/app/(drawer)/(tabs)/index.tsx.hbs | 43 +++ .../bare/app/(drawer)/(tabs)/two.tsx.hbs | 43 +++ .../app/(drawer)/_layout.tsx.hbs | 41 ++- .../native/bare/app/(drawer)/index.tsx.hbs | 249 +++++++++++++++ .../native/bare/app/+not-found.tsx.hbs | 69 ++++ .../frontend/native/bare/app/_layout.tsx.hbs | 159 ++++++++++ .../frontend/native/bare/app/modal.tsx.hbs | 37 +++ .../{nativewind => bare}/babel.config.js.hbs | 6 +- .../native/bare/components/container.tsx.hbs | 24 ++ .../bare/components/header-button.tsx.hbs | 48 +++ .../components/tabbar-icon.tsx.hbs | 1 + .../lib/android-navigation-bar.tsx.hbs | 1 + .../{nativewind => bare}/lib/constants.ts.hbs | 1 + .../native/bare/lib/use-color-scheme.ts.hbs | 20 ++ .../frontend/native/bare/metro.config.js.hbs | 9 + .../{nativewind => bare}/package.json.hbs | 3 +- .../frontend/native/bare/tsconfig.json.hbs | 11 + .../app/(drawer)/(tabs)/index.tsx.hbs | 19 -- .../app/(drawer)/(tabs)/two.tsx.hbs | 19 -- .../nativewind/app/(drawer)/index.tsx.hbs | 178 ----------- .../native/nativewind/app/+not-found.tsx.hbs | 29 -- .../native/nativewind/app/_layout.tsx.hbs | 175 ----------- .../native/nativewind/app/modal.tsx.hbs | 14 - .../nativewind/components/container.tsx.hbs | 8 - .../components/header-button.tsx.hbs | 26 -- .../frontend/native/nativewind/global.css | 50 --- .../nativewind/lib/use-color-scheme.ts.hbs | 12 - .../native/nativewind/metro.config.js.hbs | 12 - .../native/nativewind/tailwind.config.js.hbs | 59 ---- .../native/{nativewind => uniwind}/_gitignore | 12 +- .../frontend/native/uniwind/app.json.hbs | 16 + .../app/(drawer)/(tabs)/_layout.tsx.hbs | 46 +++ .../uniwind/app/(drawer)/(tabs)/index.tsx.hbs | 15 + .../uniwind/app/(drawer)/(tabs)/two.tsx.hbs | 15 + .../uniwind/app/(drawer)/_layout.tsx.hbs | 83 +++++ .../native/uniwind/app/(drawer)/index.tsx.hbs | 151 +++++++++ .../native/uniwind/app/+not-found.tsx.hbs | 32 ++ .../native/uniwind/app/_layout.tsx.hbs | 131 ++++++++ .../frontend/native/uniwind/app/modal.tsx.hbs | 53 ++++ .../uniwind/components/container.tsx.hbs | 33 ++ .../uniwind/components/theme-toggle.tsx.hbs | 35 +++ .../contexts/app-theme-context.tsx.hbs | 62 ++++ .../frontend/native/uniwind/global.css | 5 + .../native/uniwind/metro.config.js.hbs | 13 + .../frontend/native/uniwind/package.json.hbs | 54 ++++ .../{nativewind => uniwind}/tsconfig.json.hbs | 12 +- apps/cli/test/addons.test.ts | 6 +- apps/cli/test/api.test.ts | 7 +- apps/cli/test/auth.test.ts | 8 +- apps/cli/test/deployment.test.ts | 6 +- apps/cli/test/frontend.test.ts | 12 +- apps/cli/test/integration.test.ts | 8 +- 89 files changed, 2555 insertions(+), 1556 deletions(-) delete mode 100644 apps/cli/templates/auth/better-auth/convex/native/nativewind/components/sign-in.tsx.hbs delete mode 100644 apps/cli/templates/auth/better-auth/convex/native/nativewind/components/sign-up.tsx.hbs create mode 100644 apps/cli/templates/auth/better-auth/convex/native/uniwind/components/sign-in.tsx.hbs create mode 100644 apps/cli/templates/auth/better-auth/convex/native/uniwind/components/sign-up.tsx.hbs delete mode 100644 apps/cli/templates/auth/better-auth/native/nativewind/app/(drawer)/index.tsx.hbs delete mode 100644 apps/cli/templates/auth/better-auth/native/nativewind/components/sign-in.tsx.hbs delete mode 100644 apps/cli/templates/auth/better-auth/native/nativewind/components/sign-up.tsx.hbs create mode 100644 apps/cli/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs create mode 100644 apps/cli/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs create mode 100644 apps/cli/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs rename apps/cli/templates/examples/ai/native/{nativewind => uniwind}/app/(drawer)/ai.tsx.hbs (59%) rename apps/cli/templates/examples/ai/native/{nativewind => uniwind}/polyfills.js (99%) delete mode 100644 apps/cli/templates/examples/todo/native/nativewind/app/(drawer)/todos.tsx.hbs create mode 100644 apps/cli/templates/examples/todo/native/uniwind/app/(drawer)/todos.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/bare/_gitignore rename apps/cli/templates/frontend/native/{nativewind => bare}/app.json.hbs (99%) rename apps/cli/templates/frontend/native/{nativewind => bare}/app/(drawer)/(tabs)/_layout.tsx.hbs (54%) create mode 100644 apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/index.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/two.tsx.hbs rename apps/cli/templates/frontend/native/{nativewind => bare}/app/(drawer)/_layout.tsx.hbs (66%) create mode 100644 apps/cli/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/bare/app/+not-found.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/bare/app/_layout.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/bare/app/modal.tsx.hbs rename apps/cli/templates/frontend/native/{nativewind => bare}/babel.config.js.hbs (58%) create mode 100644 apps/cli/templates/frontend/native/bare/components/container.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/bare/components/header-button.tsx.hbs rename apps/cli/templates/frontend/native/{nativewind => bare}/components/tabbar-icon.tsx.hbs (99%) rename apps/cli/templates/frontend/native/{nativewind => bare}/lib/android-navigation-bar.tsx.hbs (99%) rename apps/cli/templates/frontend/native/{nativewind => bare}/lib/constants.ts.hbs (99%) create mode 100644 apps/cli/templates/frontend/native/bare/lib/use-color-scheme.ts.hbs create mode 100644 apps/cli/templates/frontend/native/bare/metro.config.js.hbs rename apps/cli/templates/frontend/native/{nativewind => bare}/package.json.hbs (96%) create mode 100644 apps/cli/templates/frontend/native/bare/tsconfig.json.hbs delete mode 100644 apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/index.tsx.hbs delete mode 100644 apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/two.tsx.hbs delete mode 100644 apps/cli/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs delete mode 100644 apps/cli/templates/frontend/native/nativewind/app/+not-found.tsx.hbs delete mode 100644 apps/cli/templates/frontend/native/nativewind/app/_layout.tsx.hbs delete mode 100644 apps/cli/templates/frontend/native/nativewind/app/modal.tsx.hbs delete mode 100644 apps/cli/templates/frontend/native/nativewind/components/container.tsx.hbs delete mode 100644 apps/cli/templates/frontend/native/nativewind/components/header-button.tsx.hbs delete mode 100644 apps/cli/templates/frontend/native/nativewind/global.css delete mode 100644 apps/cli/templates/frontend/native/nativewind/lib/use-color-scheme.ts.hbs delete mode 100644 apps/cli/templates/frontend/native/nativewind/metro.config.js.hbs delete mode 100644 apps/cli/templates/frontend/native/nativewind/tailwind.config.js.hbs rename apps/cli/templates/frontend/native/{nativewind => uniwind}/_gitignore (80%) create mode 100644 apps/cli/templates/frontend/native/uniwind/app.json.hbs create mode 100644 apps/cli/templates/frontend/native/uniwind/app/(drawer)/(tabs)/_layout.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/uniwind/app/(drawer)/(tabs)/index.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/uniwind/app/(drawer)/(tabs)/two.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/uniwind/app/(drawer)/_layout.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/uniwind/app/+not-found.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/uniwind/app/_layout.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/uniwind/app/modal.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/uniwind/components/container.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/uniwind/components/theme-toggle.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/uniwind/contexts/app-theme-context.tsx.hbs create mode 100644 apps/cli/templates/frontend/native/uniwind/global.css create mode 100644 apps/cli/templates/frontend/native/uniwind/metro.config.js.hbs create mode 100644 apps/cli/templates/frontend/native/uniwind/package.json.hbs rename apps/cli/templates/frontend/native/{nativewind => uniwind}/tsconfig.json.hbs (52%) diff --git a/apps/cli/README.md b/apps/cli/README.md index 6001e742e..7d456676f 100644 --- a/apps/cli/README.md +++ b/apps/cli/README.md @@ -57,7 +57,7 @@ Options: --orm ORM type (none, drizzle, prisma, mongoose) --auth Include authentication --no-auth Exclude authentication - --frontend Frontend types (tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, native-nativewind, native-unistyles, none) + --frontend Frontend types (tanstack-router, react-router, tanstack-start, next, nuxt, svelte, solid, native-bare, native-uniwind, native-unistyles, none) --addons Additional addons (pwa, tauri, starlight, biome, husky, turborepo, fumadocs, ultracite, oxlint, none) --examples Examples to include (todo, ai, none) --git Initialize git repository @@ -119,7 +119,7 @@ npx create-better-t-stack my-app --backend elysia --runtime node Create a project with multiple frontend options (one web + one native): ```bash -npx create-better-t-stack my-app --frontend tanstack-router native-nativewind +npx create-better-t-stack my-app --frontend tanstack-router native-bare ``` Create a project with examples: diff --git a/apps/cli/src/helpers/addons/examples-setup.ts b/apps/cli/src/helpers/addons/examples-setup.ts index 9f1ec54ef..3b80e920e 100644 --- a/apps/cli/src/helpers/addons/examples-setup.ts +++ b/apps/cli/src/helpers/addons/examples-setup.ts @@ -56,7 +56,8 @@ export async function setupExamples(config: ProjectConfig) { frontend.includes("tanstack-start"); const hasNext = frontend.includes("next"); const hasReactNative = - frontend.includes("native-nativewind") || + frontend.includes("native-bare") || + frontend.includes("native-uniwind") || frontend.includes("native-unistyles"); if (webClientDirExists) { diff --git a/apps/cli/src/helpers/addons/ultracite-setup.ts b/apps/cli/src/helpers/addons/ultracite-setup.ts index 5391ed62b..717f2ae6b 100644 --- a/apps/cli/src/helpers/addons/ultracite-setup.ts +++ b/apps/cli/src/helpers/addons/ultracite-setup.ts @@ -118,7 +118,8 @@ function getFrameworksFromFrontend(frontend: string[]): string[] { "tanstack-start": "react", next: "next", nuxt: "vue", - "native-nativewind": "react", + "native-bare": "react", + "native-uniwind": "react", "native-unistyles": "react", svelte: "svelte", solid: "solid", diff --git a/apps/cli/src/helpers/core/api-setup.ts b/apps/cli/src/helpers/core/api-setup.ts index 02a09c539..efed7f8b7 100644 --- a/apps/cli/src/helpers/core/api-setup.ts +++ b/apps/cli/src/helpers/core/api-setup.ts @@ -33,7 +33,7 @@ function getFrontendType(frontend: Frontend[]): { "tanstack-start", "next", ]; - const nativeFrontends = ["native-nativewind", "native-unistyles"]; + const nativeFrontends = ["native-bare", "native-uniwind", "native-unistyles"]; return { hasReactWeb: frontend.some((f) => reactBasedFrontends.includes(f)), @@ -149,7 +149,8 @@ function getQueryDependencies(frontend: Frontend[]) { "tanstack-router", "tanstack-start", "next", - "native-nativewind", + "native-bare", + "native-uniwind", "native-unistyles", ]; @@ -162,12 +163,14 @@ function getQueryDependencies(frontend: Frontend[]) { if (needsReactQuery) { const hasReactWeb = frontend.some( (f) => - f !== "native-nativewind" && + f !== "native-bare" && + f !== "native-uniwind" && f !== "native-unistyles" && reactBasedFrontends.includes(f), ); const hasNative = - frontend.includes("native-nativewind") || + frontend.includes("native-bare") || + frontend.includes("native-uniwind") || frontend.includes("native-unistyles"); if (hasReactWeb) { diff --git a/apps/cli/src/helpers/core/auth-setup.ts b/apps/cli/src/helpers/core/auth-setup.ts index 5dfdc6a64..f1c3c61fd 100644 --- a/apps/cli/src/helpers/core/auth-setup.ts +++ b/apps/cli/src/helpers/core/auth-setup.ts @@ -52,7 +52,8 @@ export async function setupAuth(config: ProjectConfig) { const convexBackendDirExists = await fs.pathExists(convexBackendDir); const hasNativeForBA = - frontend.includes("native-nativewind") || + frontend.includes("native-bare") || + frontend.includes("native-uniwind") || frontend.includes("native-unistyles"); if (convexBackendDirExists) { @@ -98,9 +99,10 @@ export async function setupAuth(config: ProjectConfig) { } } - const hasNativeWind = frontend.includes("native-nativewind"); + const hasNativeBare = frontend.includes("native-bare"); + const hasNativeUniwind = frontend.includes("native-uniwind"); const hasUnistyles = frontend.includes("native-unistyles"); - if (nativeDirExists && (hasNativeWind || hasUnistyles)) { + if (nativeDirExists && (hasNativeBare || hasNativeUniwind || hasUnistyles)) { await addPackageDependency({ dependencies: [ "better-auth", @@ -116,12 +118,13 @@ export async function setupAuth(config: ProjectConfig) { } } - const hasNativeWind = frontend.includes("native-nativewind"); + const hasNativeBare = frontend.includes("native-bare"); + const hasNativeUniwind = frontend.includes("native-uniwind"); const hasUnistyles = frontend.includes("native-unistyles"); if ( auth === "clerk" && nativeDirExists && - (hasNativeWind || hasUnistyles) + (hasNativeBare || hasNativeUniwind || hasUnistyles) ) { await addPackageDependency({ dependencies: ["@clerk/clerk-expo"], @@ -163,7 +166,8 @@ export async function setupAuth(config: ProjectConfig) { } if ( - (frontend.includes("native-nativewind") || + (frontend.includes("native-bare") || + frontend.includes("native-uniwind") || frontend.includes("native-unistyles")) && nativeDirExists ) { diff --git a/apps/cli/src/helpers/core/create-readme.ts b/apps/cli/src/helpers/core/create-readme.ts index 83c6aa506..ab91791f9 100644 --- a/apps/cli/src/helpers/core/create-readme.ts +++ b/apps/cli/src/helpers/core/create-readme.ts @@ -43,7 +43,8 @@ function generateReadmeContent(options: ProjectConfig) { const isConvex = backend === "convex"; const hasReactRouter = frontend.includes("react-router"); const hasNative = - frontend.includes("native-nativewind") || + frontend.includes("native-bare") || + frontend.includes("native-uniwind") || frontend.includes("native-unistyles"); const hasSvelte = frontend.includes("svelte"); @@ -321,7 +322,8 @@ function generateProjectStructure( } const hasNative = - frontend.includes("native-nativewind") || + frontend.includes("native-bare") || + frontend.includes("native-uniwind") || frontend.includes("native-unistyles"); if (hasNative) { if (isBackendSelf) { @@ -402,7 +404,8 @@ function generateFeaturesList( const hasTanstackRouter = frontend.includes("tanstack-router"); const hasReactRouter = frontend.includes("react-router"); const hasNative = - frontend.includes("native-nativewind") || + frontend.includes("native-bare") || + frontend.includes("native-uniwind") || frontend.includes("native-unistyles"); const hasNext = frontend.includes("next"); const hasTanstackStart = frontend.includes("tanstack-start"); diff --git a/apps/cli/src/helpers/core/env-setup.ts b/apps/cli/src/helpers/core/env-setup.ts index cebd39a33..e2a1cf0c7 100644 --- a/apps/cli/src/helpers/core/env-setup.ts +++ b/apps/cli/src/helpers/core/env-setup.ts @@ -225,7 +225,8 @@ export async function setupEnvironmentVariables(config: ProjectConfig) { } if ( - frontend.includes("native-nativewind") || + frontend.includes("native-bare") || + frontend.includes("native-uniwind") || frontend.includes("native-unistyles") ) { const nativeDir = path.join(projectDir, "apps/native"); @@ -277,7 +278,8 @@ export async function setupEnvironmentVariables(config: ProjectConfig) { const envLocalPath = path.join(convexBackendDir, ".env.local"); const hasNative = - frontend.includes("native-nativewind") || + frontend.includes("native-bare") || + frontend.includes("native-uniwind") || frontend.includes("native-unistyles"); const hasWeb = hasWebFrontend; diff --git a/apps/cli/src/helpers/core/post-installation.ts b/apps/cli/src/helpers/core/post-installation.ts index 103159b2e..8c96b455d 100644 --- a/apps/cli/src/helpers/core/post-installation.ts +++ b/apps/cli/src/helpers/core/post-installation.ts @@ -61,7 +61,8 @@ export async function displayPostInstallInstructions( ? getLintingInstructions(runCmd) : ""; const nativeInstructions = - frontend?.includes("native-nativewind") || + frontend?.includes("native-bare") || + frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles") ? getNativeInstructions(isConvex, isBackendSelf, frontend || []) : ""; @@ -103,7 +104,8 @@ export async function displayPostInstallInstructions( ].includes(f), ); const hasNative = - frontend?.includes("native-nativewind") || + frontend?.includes("native-bare") || + frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles"); const bunWebNativeWarning = diff --git a/apps/cli/src/helpers/core/template-manager.ts b/apps/cli/src/helpers/core/template-manager.ts index 6180bdc62..55f80dc71 100644 --- a/apps/cli/src/helpers/core/template-manager.ts +++ b/apps/cli/src/helpers/core/template-manager.ts @@ -69,9 +69,10 @@ export async function setupFrontendTemplates( const hasNuxtWeb = context.frontend.includes("nuxt"); const hasSvelteWeb = context.frontend.includes("svelte"); const hasSolidWeb = context.frontend.includes("solid"); - const hasNativeWind = context.frontend.includes("native-nativewind"); + const hasNativeBare = context.frontend.includes("native-bare"); + const hasNativeUniwind = context.frontend.includes("native-uniwind"); const hasUnistyles = context.frontend.includes("native-unistyles"); - const _hasNative = hasNativeWind || hasUnistyles; + const _hasNative = hasNativeBare || hasNativeUniwind || hasUnistyles; const isConvex = context.backend === "convex"; if (hasReactWeb || hasNuxtWeb || hasSvelteWeb || hasSolidWeb) { @@ -201,7 +202,7 @@ export async function setupFrontendTemplates( } } - if (hasNativeWind || hasUnistyles) { + if (hasNativeBare || hasNativeUniwind || hasUnistyles) { const nativeAppDir = path.join(projectDir, "apps/native"); await fs.ensureDir(nativeAppDir); @@ -220,8 +221,10 @@ export async function setupFrontendTemplates( } let nativeFrameworkPath = ""; - if (hasNativeWind) { - nativeFrameworkPath = "nativewind"; + if (hasNativeBare) { + nativeFrameworkPath = "bare"; + } else if (hasNativeUniwind) { + nativeFrameworkPath = "uniwind"; } else if (hasUnistyles) { nativeFrameworkPath = "unistyles"; } @@ -382,9 +385,10 @@ export async function setupAuthTemplate( const hasNuxtWeb = context.frontend.includes("nuxt"); const hasSvelteWeb = context.frontend.includes("svelte"); const hasSolidWeb = context.frontend.includes("solid"); - const hasNativeWind = context.frontend.includes("native-nativewind"); + const hasNativeBare = context.frontend.includes("native-bare"); + const hasUniwind = context.frontend.includes("native-uniwind"); const hasUnistyles = context.frontend.includes("native-unistyles"); - const hasNative = hasNativeWind || hasUnistyles; + const hasNative = hasNativeBare || hasUniwind || hasUnistyles; const authProvider = context.auth; @@ -440,10 +444,12 @@ export async function setupAuthTemplate( ); } - const hasNativeWind = context.frontend.includes("native-nativewind"); + const hasNativeBare = context.frontend.includes("native-bare"); + const hasUniwind = context.frontend.includes("native-uniwind"); const hasUnistyles = context.frontend.includes("native-unistyles"); let nativeFrameworkPath = ""; - if (hasNativeWind) nativeFrameworkPath = "nativewind"; + if (hasNativeBare) nativeFrameworkPath = "bare"; + else if (hasUniwind) nativeFrameworkPath = "uniwind"; else if (hasUnistyles) nativeFrameworkPath = "unistyles"; if (nativeFrameworkPath) { const convexClerkNativeFrameworkSrc = path.join( @@ -529,7 +535,7 @@ export async function setupAuthTemplate( } let nativeFrameworkPath = ""; - if (hasNativeWind) nativeFrameworkPath = "nativewind"; + if (hasUniwind) nativeFrameworkPath = "uniwind"; else if (hasUnistyles) nativeFrameworkPath = "unistyles"; if (nativeFrameworkPath) { const convexBetterAuthNativeFrameworkSrc = path.join( @@ -690,8 +696,8 @@ export async function setupAuthTemplate( } let nativeFrameworkAuthPath = ""; - if (hasNativeWind) { - nativeFrameworkAuthPath = "nativewind"; + if (hasUniwind) { + nativeFrameworkAuthPath = "uniwind"; } else if (hasUnistyles) { nativeFrameworkAuthPath = "unistyles"; } @@ -1034,13 +1040,16 @@ export async function setupExamplesTemplate( } if (nativeAppDirExists) { - const hasNativeWind = context.frontend.includes("native-nativewind"); + const hasNativeBare = context.frontend.includes("native-bare"); + const hasUniwind = context.frontend.includes("native-uniwind"); const hasUnistyles = context.frontend.includes("native-unistyles"); - if (hasNativeWind || hasUnistyles) { + if (hasNativeBare || hasUniwind || hasUnistyles) { let nativeFramework = ""; - if (hasNativeWind) { - nativeFramework = "nativewind"; + if (hasNativeBare) { + nativeFramework = "bare"; + } else if (hasUniwind) { + nativeFramework = "uniwind"; } else if (hasUnistyles) { nativeFramework = "unistyles"; } @@ -1065,9 +1074,10 @@ export async function setupExamplesTemplate( export async function handleExtras(projectDir: string, context: ProjectConfig) { const extrasDir = path.join(PKG_ROOT, "templates/extras"); - const hasNativeWind = context.frontend.includes("native-nativewind"); + const hasNativeBare = context.frontend.includes("native-bare"); + const hasUniwind = context.frontend.includes("native-uniwind"); const hasUnistyles = context.frontend.includes("native-unistyles"); - const hasNative = hasNativeWind || hasUnistyles; + const hasNative = hasNativeBare || hasUniwind || hasUnistyles; if (context.packageManager === "pnpm") { const pnpmWorkspaceSrc = path.join(extrasDir, "pnpm-workspace.yaml"); diff --git a/apps/cli/src/prompts/auth.ts b/apps/cli/src/prompts/auth.ts index a216d8dc4..f46cac0d7 100644 --- a/apps/cli/src/prompts/auth.ts +++ b/apps/cli/src/prompts/auth.ts @@ -18,7 +18,8 @@ export async function getAuthChoice( "tanstack-router", "tanstack-start", "next", - "native-nativewind", + "native-bare", + "native-uniwind", "native-unistyles", ].includes(f), ); @@ -29,7 +30,8 @@ export async function getAuthChoice( "tanstack-router", "tanstack-start", "next", - "native-nativewind", + "native-bare", + "native-uniwind", "native-unistyles", ].includes(f), ); diff --git a/apps/cli/src/prompts/frontend.ts b/apps/cli/src/prompts/frontend.ts index f4079c857..52282e4b9 100644 --- a/apps/cli/src/prompts/frontend.ts +++ b/apps/cli/src/prompts/frontend.ts @@ -92,8 +92,13 @@ export async function getFrontendChoice( message: "Choose native", options: [ { - value: "native-nativewind" as const, - label: "NativeWind", + value: "native-bare" as const, + label: "Bare", + hint: "Bare Expo without styling library", + }, + { + value: "native-uniwind" as const, + label: "UniWind", hint: "Use Tailwind CSS for React Native", }, { @@ -102,7 +107,7 @@ export async function getFrontendChoice( hint: "Consistent styling for React Native", }, ], - initialValue: "native-nativewind", + initialValue: "native-bare", }); if (isCancel(nativeFramework)) return exitCancelled("Operation cancelled"); diff --git a/apps/cli/src/types.ts b/apps/cli/src/types.ts index 7fb4d6c7a..4792ac343 100644 --- a/apps/cli/src/types.ts +++ b/apps/cli/src/types.ts @@ -27,7 +27,8 @@ export const FrontendSchema = z "tanstack-start", "next", "nuxt", - "native-nativewind", + "native-bare", + "native-uniwind", "native-unistyles", "svelte", "solid", diff --git a/apps/cli/src/utils/better-auth-plugin-setup.ts b/apps/cli/src/utils/better-auth-plugin-setup.ts index 7327a69a5..1c3f86c23 100644 --- a/apps/cli/src/utils/better-auth-plugin-setup.ts +++ b/apps/cli/src/utils/better-auth-plugin-setup.ts @@ -32,7 +32,8 @@ export async function setupBetterAuthPlugins( } if ( - config.frontend?.includes("native-nativewind") || + config.frontend?.includes("native-bare") || + config.frontend?.includes("native-uniwind") || config.frontend?.includes("native-unistyles") ) { pluginsToAdd.push("expo()"); diff --git a/apps/cli/src/utils/compatibility-rules.ts b/apps/cli/src/utils/compatibility-rules.ts index f49e19983..b4c51515e 100644 --- a/apps/cli/src/utils/compatibility-rules.ts +++ b/apps/cli/src/utils/compatibility-rules.ts @@ -24,7 +24,7 @@ export function splitFrontends(values: Frontend[] = []): { } { const web = values.filter((f) => isWebFrontend(f)); const native = values.filter( - (f) => f === "native-nativewind" || f === "native-unistyles", + (f) => f === "native-bare" || f === "native-uniwind" || f === "native-unistyles", ); return { web, native }; } @@ -38,7 +38,7 @@ export function ensureSingleWebAndNative(frontends: Frontend[]) { } if (native.length > 1) { exitWithError( - "Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles", + "Cannot select multiple native frameworks. Choose only one of: native-bare, native-uniwind, native-unistyles", ); } } @@ -72,7 +72,7 @@ export function validateSelfBackendCompatibility( if (native.length > 1) { exitWithError( - "Cannot select multiple native frameworks. Choose only one of: native-nativewind, native-unistyles", + "Cannot select multiple native frameworks. Choose only one of: native-bare, native-uniwind, native-unistyles", ); } } diff --git a/apps/cli/src/utils/config-validation.ts b/apps/cli/src/utils/config-validation.ts index cec391e8b..4bbcf5ee1 100644 --- a/apps/cli/src/utils/config-validation.ts +++ b/apps/cli/src/utils/config-validation.ts @@ -215,7 +215,8 @@ export function validateConvexConstraints( "tanstack-router", "tanstack-start", "next", - "native-nativewind", + "native-bare", + "native-uniwind", "native-unistyles", ]; const hasSupportedFrontend = config.frontend?.some((f) => diff --git a/apps/cli/src/utils/setup-catalogs.ts b/apps/cli/src/utils/setup-catalogs.ts index 7548c5603..e9031a7a8 100644 --- a/apps/cli/src/utils/setup-catalogs.ts +++ b/apps/cli/src/utils/setup-catalogs.ts @@ -25,7 +25,7 @@ export async function setupCatalogs( const packagePaths = [ "apps/server", "apps/web", - // "apps/native", // todo + "apps/native", "apps/fumadocs", "apps/docs", "packages/api", diff --git a/apps/cli/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs b/apps/cli/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs index 022194141..4e7f277d3 100644 --- a/apps/cli/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs +++ b/apps/cli/templates/auth/better-auth/convex/backend/convex/auth.ts.hbs @@ -1,5 +1,5 @@ import { createClient, type GenericCtx } from "@convex-dev/better-auth"; -{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}} +{{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}} import { convex } from "@convex-dev/better-auth/plugins"; import { expo } from "@better-auth/expo"; {{else}} @@ -17,7 +17,7 @@ import { v } from "convex/values"; {{#if (or (includes frontend "tanstack-start") (includes frontend "next") (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}} const siteUrl = process.env.SITE_URL!; {{/if}} -{{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}} +{{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}} const nativeAppUrl = process.env.NATIVE_APP_URL || "mybettertapp://"; {{/if}} @@ -31,10 +31,10 @@ function createAuth( logger: { disabled: optionsOnly, }, - {{#if (and (or (includes frontend "native-nativewind") (includes frontend "native-unistyles")) (or (includes frontend "tanstack-start") (includes frontend "next")))}} + {{#if (and (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles")) (or (includes frontend "tanstack-start") (includes frontend "next")))}} baseURL: siteUrl, trustedOrigins: [siteUrl, nativeAppUrl], - {{else if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}} + {{else if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}} trustedOrigins: [nativeAppUrl], {{else if (or (includes frontend "tanstack-start") (includes frontend "next"))}} baseURL: siteUrl, @@ -48,7 +48,7 @@ function createAuth( requireEmailVerification: false, }, plugins: [ - {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}} + {{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}} expo(), {{/if}} {{#if (or (includes frontend "tanstack-router") (includes frontend "react-router") (includes frontend "nuxt") (includes frontend "svelte") (includes frontend "solid"))}} diff --git a/apps/cli/templates/auth/better-auth/convex/backend/convex/http.ts.hbs b/apps/cli/templates/auth/better-auth/convex/backend/convex/http.ts.hbs index 248100890..7dd37e868 100644 --- a/apps/cli/templates/auth/better-auth/convex/backend/convex/http.ts.hbs +++ b/apps/cli/templates/auth/better-auth/convex/backend/convex/http.ts.hbs @@ -3,7 +3,7 @@ import { authComponent, createAuth } from "./auth"; const http = httpRouter(); -{{#if (or (includes frontend "tanstack-start") (includes frontend "next") (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}} +{{#if (or (includes frontend "tanstack-start") (includes frontend "next") (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}} authComponent.registerRoutes(http, createAuth); {{else}} authComponent.registerRoutes(http, createAuth, { cors: true }); diff --git a/apps/cli/templates/auth/better-auth/convex/native/nativewind/components/sign-in.tsx.hbs b/apps/cli/templates/auth/better-auth/convex/native/nativewind/components/sign-in.tsx.hbs deleted file mode 100644 index 91f66ee43..000000000 --- a/apps/cli/templates/auth/better-auth/convex/native/nativewind/components/sign-in.tsx.hbs +++ /dev/null @@ -1,86 +0,0 @@ -import { authClient } from "@/lib/auth-client"; -import { useState } from "react"; -import { - ActivityIndicator, - Text, - TextInput, - TouchableOpacity, - View, -} from "react-native"; - -export function SignIn() { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - const handleLogin = async () => { - setIsLoading(true); - setError(null); - - await authClient.signIn.email( - { - email, - password, - }, - { - onError: (error) => { - setError(error.error?.message || "Failed to sign in"); - setIsLoading(false); - }, - onSuccess: () => { - setEmail(""); - setPassword(""); - }, - onFinished: () => { - setIsLoading(false); - }, - }, - ); - }; - - return ( - - - Sign In - - - {error && ( - - {error} - - )} - - - - - - - {isLoading ? ( - - ) : ( - Sign In - )} - - - ); -} \ No newline at end of file diff --git a/apps/cli/templates/auth/better-auth/convex/native/nativewind/components/sign-up.tsx.hbs b/apps/cli/templates/auth/better-auth/convex/native/nativewind/components/sign-up.tsx.hbs deleted file mode 100644 index 93ed2f5b0..000000000 --- a/apps/cli/templates/auth/better-auth/convex/native/nativewind/components/sign-up.tsx.hbs +++ /dev/null @@ -1,97 +0,0 @@ -import { authClient } from "@/lib/auth-client"; -import { useState } from "react"; -import { - ActivityIndicator, - Text, - TextInput, - TouchableOpacity, - View, -} from "react-native"; - -export function SignUp() { - const [name, setName] = useState(""); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - const handleSignUp = async () => { - setIsLoading(true); - setError(null); - - await authClient.signUp.email( - { - name, - email, - password, - }, - { - onError: (error) => { - setError(error.error?.message || "Failed to sign up"); - setIsLoading(false); - }, - onSuccess: () => { - setName(""); - setEmail(""); - setPassword(""); - }, - onFinished: () => { - setIsLoading(false); - }, - }, - ); - }; - - return ( - - - Create Account - - - {error && ( - - {error} - - )} - - - - - - - - - {isLoading ? ( - - ) : ( - Sign Up - )} - - - ); -} diff --git a/apps/cli/templates/auth/better-auth/convex/native/uniwind/components/sign-in.tsx.hbs b/apps/cli/templates/auth/better-auth/convex/native/uniwind/components/sign-in.tsx.hbs new file mode 100644 index 000000000..9fad45969 --- /dev/null +++ b/apps/cli/templates/auth/better-auth/convex/native/uniwind/components/sign-in.tsx.hbs @@ -0,0 +1,91 @@ +import { authClient } from "@/lib/auth-client"; +import { useState } from "react"; +import { + ActivityIndicator, + Text, + TextInput, + Pressable, + View, +} from "react-native"; +import { Card, useThemeColor } from "heroui-native"; + +export function SignIn() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const mutedColor = useThemeColor("muted"); + const accentColor = useThemeColor("accent"); + const foregroundColor = useThemeColor("foreground"); + const dangerColor = useThemeColor("danger"); + + const handleLogin = async () => { + setIsLoading(true); + setError(null); + + await authClient.signIn.email( + { + email, + password, + }, + { + onError: (error) => { + setError(error.error?.message || "Failed to sign in"); + setIsLoading(false); + }, + onSuccess: () => { + setEmail(""); + setPassword(""); + }, + onFinished: () => { + setIsLoading(false); + }, + }, + ); + }; + + return ( + + Sign In + + {error && ( + + {error} + + )} + + + + + + + {isLoading ? ( + + ) : ( + Sign In + )} + + + ); +} + diff --git a/apps/cli/templates/auth/better-auth/convex/native/uniwind/components/sign-up.tsx.hbs b/apps/cli/templates/auth/better-auth/convex/native/uniwind/components/sign-up.tsx.hbs new file mode 100644 index 000000000..e983a861b --- /dev/null +++ b/apps/cli/templates/auth/better-auth/convex/native/uniwind/components/sign-up.tsx.hbs @@ -0,0 +1,102 @@ +import { authClient } from "@/lib/auth-client"; +import { useState } from "react"; +import { + ActivityIndicator, + Text, + TextInput, + Pressable, + View, +} from "react-native"; +import { Card, useThemeColor } from "heroui-native"; + +export function SignUp() { + const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const mutedColor = useThemeColor("muted"); + const accentColor = useThemeColor("accent"); + const foregroundColor = useThemeColor("foreground"); + const dangerColor = useThemeColor("danger"); + + const handleSignUp = async () => { + setIsLoading(true); + setError(null); + + await authClient.signUp.email( + { + name, + email, + password, + }, + { + onError: (error) => { + setError(error.error?.message || "Failed to sign up"); + setIsLoading(false); + }, + onSuccess: () => { + setName(""); + setEmail(""); + setPassword(""); + }, + onFinished: () => { + setIsLoading(false); + }, + }, + ); + }; + + return ( + + Create Account + + {error && ( + + {error} + + )} + + + + + + + + + {isLoading ? ( + + ) : ( + Sign Up + )} + + + ); +} + diff --git a/apps/cli/templates/auth/better-auth/native/nativewind/app/(drawer)/index.tsx.hbs b/apps/cli/templates/auth/better-auth/native/nativewind/app/(drawer)/index.tsx.hbs deleted file mode 100644 index f6954b616..000000000 --- a/apps/cli/templates/auth/better-auth/native/nativewind/app/(drawer)/index.tsx.hbs +++ /dev/null @@ -1,95 +0,0 @@ -import { authClient } from "@/lib/auth-client"; -import { useQuery } from "@tanstack/react-query"; -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 ( - - - - - BETTER T STACK - - {session?.user ? ( - - - - Welcome,{" "} - {session.user.name} - - - - {session.user.email} - - - { - authClient.signOut(); - queryClient.invalidateQueries(); - }} - > - Sign Out - - - ) : null} - - API Status - - - - {healthCheck.isLoading - ? "Checking..." - : healthCheck.data - ? "Connected to API" - : "API Disconnected"} - - - - - - Private Data - - {privateData && ( - - - {privateData.data?.message} - - - )} - - {!session?.user && ( - <> - - - - )} - - - - ); -} diff --git a/apps/cli/templates/auth/better-auth/native/nativewind/components/sign-in.tsx.hbs b/apps/cli/templates/auth/better-auth/native/nativewind/components/sign-in.tsx.hbs deleted file mode 100644 index 244627b81..000000000 --- a/apps/cli/templates/auth/better-auth/native/nativewind/components/sign-in.tsx.hbs +++ /dev/null @@ -1,93 +0,0 @@ -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, - Text, - TextInput, - TouchableOpacity, - View, -} from "react-native"; - -export function SignIn() { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - const handleLogin = async () => { - setIsLoading(true); - setError(null); - - await authClient.signIn.email( - { - email, - password, - }, - { - onError: (error) => { - setError(error.error?.message || "Failed to sign in"); - setIsLoading(false); - }, - onSuccess: () => { - setEmail(""); - setPassword(""); - queryClient.refetchQueries(); - }, - onFinished: () => { - setIsLoading(false); - }, - }, - ); - }; - - return ( - - - Sign In - - - {error && ( - - {error} - - )} - - - - - - - {isLoading ? ( - - ) : ( - Sign In - )} - - - ); -} \ No newline at end of file diff --git a/apps/cli/templates/auth/better-auth/native/nativewind/components/sign-up.tsx.hbs b/apps/cli/templates/auth/better-auth/native/nativewind/components/sign-up.tsx.hbs deleted file mode 100644 index bccd9c73e..000000000 --- a/apps/cli/templates/auth/better-auth/native/nativewind/components/sign-up.tsx.hbs +++ /dev/null @@ -1,104 +0,0 @@ -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, - Text, - TextInput, - TouchableOpacity, - View, -} from "react-native"; - -export function SignUp() { - const [name, setName] = useState(""); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); - - const handleSignUp = async () => { - setIsLoading(true); - setError(null); - - await authClient.signUp.email( - { - name, - email, - password, - }, - { - onError: (error) => { - setError(error.error?.message || "Failed to sign up"); - setIsLoading(false); - }, - onSuccess: () => { - setName(""); - setEmail(""); - setPassword(""); - queryClient.refetchQueries(); - }, - onFinished: () => { - setIsLoading(false); - }, - }, - ); - }; - - return ( - - - Create Account - - - {error && ( - - {error} - - )} - - - - - - - - - {isLoading ? ( - - ) : ( - Sign Up - )} - - - ); -} diff --git a/apps/cli/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs b/apps/cli/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs new file mode 100644 index 000000000..37a6be3bc --- /dev/null +++ b/apps/cli/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs @@ -0,0 +1,123 @@ +import { Text, View, Pressable } from "react-native"; +import { Container } from "@/components/container"; +import { authClient } from "@/lib/auth-client"; +import { useQuery } from "@tanstack/react-query"; +import { Ionicons } from "@expo/vector-icons"; +import { Card, Chip, useThemeColor } from "heroui-native"; +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(); + + const mutedColor = useThemeColor("muted"); + const successColor = useThemeColor("success"); + const dangerColor = useThemeColor("danger"); + const foregroundColor = useThemeColor("foreground"); + + const isConnected = healthCheck?.data === "OK"; + const isLoading = healthCheck?.isLoading; + + return ( + + + + BETTER T STACK + + + + {session?.user ? ( + + + Welcome, {session.user.name} + + + {session.user.email} + + { + authClient.signOut(); + queryClient.invalidateQueries(); + }} + > + Sign Out + + + ) : null} + + + + System Status + + {isConnected ? "LIVE" : "OFFLINE"} + + + + + + + + + {{#if (eq api "orpc")}}ORPC{{else}}TRPC{{/if}} Backend + + + {isLoading + ? "Checking connection..." + : isConnected + ? "Connected to API" + : "API Disconnected"} + + + {isLoading && ( + + )} + {!isLoading && isConnected && ( + + )} + {!isLoading && !isConnected && ( + + )} + + + + + + Private Data + {privateData && ( + + {privateData.data?.message} + + )} + + + {!session?.user && ( + <> + + + + )} + + ); +} + diff --git a/apps/cli/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs b/apps/cli/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs new file mode 100644 index 000000000..2c428fab5 --- /dev/null +++ b/apps/cli/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs @@ -0,0 +1,99 @@ +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, + Text, + TextInput, + Pressable, + View, +} from "react-native"; +import { Card, useThemeColor } from "heroui-native"; + +function SignIn() { + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const mutedColor = useThemeColor("muted"); + const accentColor = useThemeColor("accent"); + const foregroundColor = useThemeColor("foreground"); + const dangerColor = useThemeColor("danger"); + + async function handleLogin() { + setIsLoading(true); + setError(null); + + await authClient.signIn.email( + { + email, + password, + }, + { + onError(error) { + setError(error.error?.message || "Failed to sign in"); + setIsLoading(false); + }, + onSuccess() { + setEmail(""); + setPassword(""); + queryClient.refetchQueries(); + }, + onFinished() { + setIsLoading(false); + }, + } + ); + } + + return ( + + Sign In + + {error ? ( + + {error} + + ) : null} + + + + + + + {isLoading ? ( + + ) : ( + Sign In + )} + + + ); +} + +export { SignIn }; \ No newline at end of file diff --git a/apps/cli/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs b/apps/cli/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs new file mode 100644 index 000000000..3d22b08d2 --- /dev/null +++ b/apps/cli/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs @@ -0,0 +1,130 @@ +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, + Text, + TextInput, + Pressable, + View, +} from "react-native"; +import { Card, useThemeColor } from "heroui-native"; + +function signUpHandler({ + name, + email, + password, + setError, + setIsLoading, + setName, + setEmail, + setPassword, +}) { + setIsLoading(true); + setError(null); + + authClient.signUp.email( + { + name, + email, + password, + }, + { + onError(error) { + setError(error.error?.message || "Failed to sign up"); + setIsLoading(false); + }, + onSuccess() { + setName(""); + setEmail(""); + setPassword(""); + queryClient.refetchQueries(); + }, + onFinished() { + setIsLoading(false); + }, + } + ); +} + +export function SignUp() { + const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + const mutedColor = useThemeColor("muted"); + const accentColor = useThemeColor("accent"); + const foregroundColor = useThemeColor("foreground"); + const dangerColor = useThemeColor("danger"); + + function handlePress() { + signUpHandler({ + name, + email, + password, + setError, + setIsLoading, + setName, + setEmail, + setPassword, + }); + } + + return ( + + Create Account + + {error && ( + + {error} + + )} + + + + + + + + + {isLoading ? ( + + ) : ( + Sign Up + )} + + + ); +} \ No newline at end of file diff --git a/apps/cli/templates/auth/better-auth/server/base/src/index.ts.hbs b/apps/cli/templates/auth/better-auth/server/base/src/index.ts.hbs index 3924685a0..c386f36bb 100644 --- a/apps/cli/templates/auth/better-auth/server/base/src/index.ts.hbs +++ b/apps/cli/templates/auth/better-auth/server/base/src/index.ts.hbs @@ -16,7 +16,7 @@ export const auth = betterAuth({ }), trustedOrigins: [ process.env.CORS_ORIGIN || "", - {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}} + {{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}} "mybettertapp://", "exp://" {{/if}} ], @@ -77,7 +77,7 @@ export const auth = betterAuth({ }), trustedOrigins: [ process.env.CORS_ORIGIN || "", - {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}} + {{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}} "mybettertapp://", "exp://" {{/if}} ], @@ -138,7 +138,7 @@ export const auth = betterAuth({ }), trustedOrigins: [ env.CORS_ORIGIN, - {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}} + {{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}} "mybettertapp://", "exp://" {{/if}} ], @@ -206,7 +206,7 @@ export const auth = betterAuth({ database: mongodbAdapter(client), trustedOrigins: [ process.env.CORS_ORIGIN || "", - {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}} + {{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}} "mybettertapp://", "exp://" {{/if}} ], @@ -258,7 +258,7 @@ export const auth = betterAuth({ database: "", // Invalid configuration trustedOrigins: [ process.env.CORS_ORIGIN || "", - {{#if (or (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}} + {{#if (or (includes frontend "native-bare") (includes frontend "native-uniwind") (includes frontend "native-unistyles"))}} "mybettertapp://", "exp://" {{/if}} ], diff --git a/apps/cli/templates/examples/ai/native/nativewind/app/(drawer)/ai.tsx.hbs b/apps/cli/templates/examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs similarity index 59% rename from apps/cli/templates/examples/ai/native/nativewind/app/(drawer)/ai.tsx.hbs rename to apps/cli/templates/examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs index e68089c64..6c3f9c260 100644 --- a/apps/cli/templates/examples/ai/native/nativewind/app/(drawer)/ai.tsx.hbs +++ b/apps/cli/templates/examples/ai/native/uniwind/app/(drawer)/ai.tsx.hbs @@ -3,7 +3,7 @@ import { View, Text, TextInput, - TouchableOpacity, + Pressable, ScrollView, KeyboardAvoidingView, Platform, @@ -13,14 +13,16 @@ import { DefaultChatTransport } from "ai"; import { fetch as expoFetch } from "expo/fetch"; import { Ionicons } from "@expo/vector-icons"; import { Container } from "@/components/container"; +import { Card, useThemeColor } from "heroui-native"; const generateAPIUrl = (relativePath: string) => { const serverUrl = process.env.EXPO_PUBLIC_SERVER_URL; if (!serverUrl) { - throw new Error("EXPO_PUBLIC_SERVER_URL environment variable is not defined"); + throw new Error( + "EXPO_PUBLIC_SERVER_URL environment variable is not defined" + ); } - - const path = relativePath.startsWith('/') ? relativePath : `/${relativePath}`; + const path = relativePath.startsWith("/") ? relativePath : `/${relativePath}`; return serverUrl.concat(path); }; @@ -29,12 +31,15 @@ export default function AIScreen() { const { messages, error, sendMessage } = useChat({ transport: new DefaultChatTransport({ fetch: expoFetch as unknown as typeof globalThis.fetch, - api: generateAPIUrl('/ai'), + api: generateAPIUrl("/ai"), }), - onError: error => console.error(error, 'AI Chat Error'), + onError: (error) => console.error(error, "AI Chat Error"), }); - const scrollViewRef = useRef(null); + const mutedColor = useThemeColor("muted"); + const accentColor = useThemeColor("accent"); + const foregroundColor = useThemeColor("foreground"); + const dangerColor = useThemeColor("danger"); useEffect(() => { scrollViewRef.current?.scrollToEnd({ animated: true }); @@ -52,12 +57,14 @@ export default function AIScreen() { return ( - - Error: {error.message} - - - Please check your connection and try again. - + + + Error: {error.message} + + + Please check your connection and try again. + + ); @@ -65,7 +72,7 @@ export default function AIScreen() { return ( - @@ -74,11 +81,10 @@ export default function AIScreen() { AI Chat - + Chat with our AI assistant - {messages.length === 0 ? ( - + Ask me anything to get started! ) : ( - + {messages.map((message) => ( - {message.role === "user" ? "You" : "AI Assistant"} - - {message.parts.map((part, i) => { - if (part.type === 'text') { - return ( - - {part.text} - - ); - } - return ( + + {message.parts.map((part, i) => + part.type === "text" ? ( + + {part.text} + + ) : ( {JSON.stringify(part)} - ); - })} + ) + )} - + ))} )} - - - + + { e.preventDefault(); onSubmit(); }} autoFocus={true} /> - - + ); -} \ No newline at end of file +} \ No newline at end of file diff --git a/apps/cli/templates/examples/ai/native/nativewind/polyfills.js b/apps/cli/templates/examples/ai/native/uniwind/polyfills.js similarity index 99% rename from apps/cli/templates/examples/ai/native/nativewind/polyfills.js rename to apps/cli/templates/examples/ai/native/uniwind/polyfills.js index 8e2e56f50..8bbb2b269 100644 --- a/apps/cli/templates/examples/ai/native/nativewind/polyfills.js +++ b/apps/cli/templates/examples/ai/native/uniwind/polyfills.js @@ -23,3 +23,4 @@ if (Platform.OS !== "web") { } export {}; + diff --git a/apps/cli/templates/examples/todo/native/nativewind/app/(drawer)/todos.tsx.hbs b/apps/cli/templates/examples/todo/native/nativewind/app/(drawer)/todos.tsx.hbs deleted file mode 100644 index f2bc8fbdf..000000000 --- a/apps/cli/templates/examples/todo/native/nativewind/app/(drawer)/todos.tsx.hbs +++ /dev/null @@ -1,295 +0,0 @@ -import { useState } from "react"; -import { - View, - Text, - TextInput, - TouchableOpacity, - ScrollView, - ActivityIndicator, - Alert, -} from "react-native"; -import { Ionicons } from "@expo/vector-icons"; -{{#if (eq backend "convex")}} -import { useMutation, useQuery } from "convex/react"; -import { api } from "@{{projectName}}/backend/convex/_generated/api"; -import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel"; -{{else}} -import { useMutation, useQuery } from "@tanstack/react-query"; -{{/if}} - -import { Container } from "@/components/container"; -{{#unless (eq backend "convex")}} -{{#if (eq api "orpc")}} -import { orpc } from "@/utils/orpc"; -{{/if}} -{{#if (eq api "trpc")}} -import { trpc } from "@/utils/trpc"; -{{/if}} -{{/unless}} - -export default function TodosScreen() { - const [newTodoText, setNewTodoText] = useState(""); - - {{#if (eq backend "convex")}} - const todos = useQuery(api.todos.getAll); - const createTodoMutation = useMutation(api.todos.create); - const toggleTodoMutation = useMutation(api.todos.toggle); - const deleteTodoMutation = useMutation(api.todos.deleteTodo); - - const handleAddTodo = async () => { - const text = newTodoText.trim(); - if (!text) return; - await createTodoMutation({ text }); - setNewTodoText(""); - }; - - const handleToggleTodo = (id: Id<"todos">, currentCompleted: boolean) => { - toggleTodoMutation({ id, completed: !currentCompleted }); - }; - - const handleDeleteTodo = (id: Id<"todos">) => { - Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [ - { text: "Cancel", style: "cancel" }, - { - text: "Delete", - style: "destructive", - onPress: () => deleteTodoMutation({ id }), - }, - ]); - }; - {{else}} - {{#if (eq api "orpc")}} - const todos = useQuery(orpc.todo.getAll.queryOptions()); - const createMutation = useMutation( - orpc.todo.create.mutationOptions({ - onSuccess: () => { - todos.refetch(); - setNewTodoText(""); - }, - }), - ); - const toggleMutation = useMutation( - orpc.todo.toggle.mutationOptions({ - onSuccess: () => { todos.refetch() }, - }), - ); - const deleteMutation = useMutation( - orpc.todo.delete.mutationOptions({ - onSuccess: () => { todos.refetch() }, - }), - ); - {{/if}} - {{#if (eq api "trpc")}} - const todos = useQuery(trpc.todo.getAll.queryOptions()); - const createMutation = useMutation( - trpc.todo.create.mutationOptions({ - onSuccess: () => { - todos.refetch(); - setNewTodoText(""); - }, - }), - ); - const toggleMutation = useMutation( - trpc.todo.toggle.mutationOptions({ - onSuccess: () => { todos.refetch() }, - }), - ); - const deleteMutation = useMutation( - trpc.todo.delete.mutationOptions({ - onSuccess: () => { todos.refetch() }, - }), - ); - {{/if}} - - const handleAddTodo = () => { - if (newTodoText.trim()) { - createMutation.mutate({ text: newTodoText }); - } - }; - - const handleToggleTodo = (id: number, completed: boolean) => { - toggleMutation.mutate({ id, completed: !completed }); - }; - - const handleDeleteTodo = (id: number) => { - Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [ - { text: "Cancel", style: "cancel" }, - { - text: "Delete", - style: "destructive", - onPress: () => deleteMutation.mutate({ id }), - }, - ]); - }; - {{/if}} - - return ( - - - - - - Todo List - - - Manage your tasks efficiently - - - - - - - {{#if (eq backend "convex")}} - Add - {{else}} - {createMutation.isPending ? ( - - ) : ( - Add - )} - {{/if}} - - - - - {{#if (eq backend "convex")}} - {todos === undefined ? ( - - - - ) : todos.length === 0 ? ( - - No todos yet. Add one above! - - ) : ( - - {todos.map((todo) => ( - - - - handleToggleTodo(todo._id, todo.completed) - } - className="mr-3" - > - - - - {todo.text} - - - handleDeleteTodo(todo._id)} - className="ml-2 p-1" - > - - - - ))} - - )} - {{else}} - {todos.isLoading ? ( - - - - ) : todos.data?.length === 0 ? ( - - No todos yet. Add one above! - - ) : ( - - {todos.data?.map((todo) => ( - - - - handleToggleTodo(todo.id, todo.completed) - } - className="mr-3" - > - - - - {todo.text} - - - handleDeleteTodo(todo.id)} - className="ml-2 p-1" - > - - - - ))} - - )} - {{/if}} - - - - - ); -} diff --git a/apps/cli/templates/examples/todo/native/uniwind/app/(drawer)/todos.tsx.hbs b/apps/cli/templates/examples/todo/native/uniwind/app/(drawer)/todos.tsx.hbs new file mode 100644 index 000000000..69566b80b --- /dev/null +++ b/apps/cli/templates/examples/todo/native/uniwind/app/(drawer)/todos.tsx.hbs @@ -0,0 +1,295 @@ +import { useState } from "react"; +import { + View, + Text, + TextInput, + ScrollView, + ActivityIndicator, + Alert, + Pressable, +} from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +{{#if (eq backend "convex")}} +import { useMutation, useQuery } from "convex/react"; +import { api } from "@{{projectName}}/backend/convex/_generated/api"; +import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel"; +{{else}} +import { useMutation, useQuery } from "@tanstack/react-query"; +{{/if}} +import { Container } from "@/components/container"; +{{#unless (eq backend "convex")}} + {{#if (eq api "orpc")}} + import { orpc } from "@/utils/orpc"; + {{/if}} + {{#if (eq api "trpc")}} + import { trpc } from "@/utils/trpc"; + {{/if}} +{{/unless}} +import { Card, Checkbox, useThemeColor, Chip } from "heroui-native"; + +export default function TodosScreen() { + const [newTodoText, setNewTodoText] = useState(""); + {{#if (eq backend "convex")}} + const todos = useQuery(api.todos.getAll); + const createTodoMutation = useMutation(api.todos.create); + const toggleTodoMutation = useMutation(api.todos.toggle); + const deleteTodoMutation = useMutation(api.todos.deleteTodo); + {{else}} + {{#if (eq api "orpc")}} + const todos = useQuery(orpc.todo.getAll.queryOptions()); + const createMutation = useMutation(orpc.todo.create.mutationOptions({ + onSuccess: () => { + todos.refetch(); + setNewTodoText(""); + }, + })); + const toggleMutation = useMutation(orpc.todo.toggle.mutationOptions({ + onSuccess: () => { + todos.refetch(); + }, + })); + const deleteMutation = useMutation(orpc.todo.delete.mutationOptions({ + onSuccess: () => { + todos.refetch(); + }, + })); + {{/if}} + {{#if (eq api "trpc")}} + const todos = useQuery(trpc.todo.getAll.queryOptions()); + const createMutation = useMutation(trpc.todo.create.mutationOptions({ + onSuccess: () => { + todos.refetch(); + setNewTodoText(""); + }, + })); + const toggleMutation = useMutation(trpc.todo.toggle.mutationOptions({ + onSuccess: () => { + todos.refetch(); + }, + })); + const deleteMutation = useMutation(trpc.todo.delete.mutationOptions({ + onSuccess: () => { + todos.refetch(); + }, + })); + {{/if}} + {{/if}} + + const mutedColor = useThemeColor("muted"); + const accentColor = useThemeColor("accent"); + const dangerColor = useThemeColor("danger"); + const foregroundColor = useThemeColor("foreground"); + + {{#if (eq backend "convex")}} + const handleAddTodo = async () => { + const text = newTodoText.trim(); + if (!text) return; + await createTodoMutation({ text }); + setNewTodoText(""); + }; + + const handleToggleTodo = (id: Id<"todos">, currentCompleted: boolean) => { + toggleTodoMutation({ id, completed: !currentCompleted }); + }; + + const handleDeleteTodo = (id: Id<"todos">) => { + Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: () => deleteTodoMutation({ id }), + }, + ]); + }; + + const isLoading = !todos; + const completedCount = todos?.filter((t) => t.completed).length || 0; + const totalCount = todos?.length || 0; + {{else}} + const handleAddTodo = () => { + if (newTodoText.trim()) { + createMutation.mutate({ text: newTodoText }); + } + }; + + const handleToggleTodo = (id: number, completed: boolean) => { + toggleMutation.mutate({ id, completed: !completed }); + }; + + const handleDeleteTodo = (id: number) => { + Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: () => deleteMutation.mutate({ id }), + }, + ]); + }; + + const isLoading = todos?.isLoading; + const completedCount = todos?.data?.filter((t) => t.completed).length || 0; + const totalCount = todos?.data?.length || 0; + {{/if}} + + return ( + + + + + + Todo List + + {totalCount > 0 && ( + + + {completedCount}/{totalCount} + + + )} + + + + + + + + + + {{#if (eq backend "convex")}} + + {{else}} + {createMutation.isPending ? ( + + ) : ( + + )} + {{/if}} + + + + + {{#if (eq backend "convex")}} + {isLoading && ( + + + Loading todos... + + )} + + {todos && todos.length === 0 && !isLoading && ( + + + + No todos yet + + + Add your first task to get started! + + + )} + + {todos && todos.length > 0 && ( + + {todos.map((todo) => ( + + + handleToggleTodo(todo._id, todo.completed)} + /> + + + {todo.text} + + + handleDeleteTodo(todo._id)} + className="p-2 rounded-lg active:opacity-70" + > + + + + + ))} + + )} + {{else}} + {isLoading && ( + + + Loading todos... + + )} + + {todos?.data && todos.data.length === 0 && !isLoading && ( + + + + No todos yet + + + Add your first task to get started! + + + )} + + {todos?.data && todos.data.length > 0 && ( + + {todos.data.map((todo) => ( + + + handleToggleTodo(todo.id, todo.completed)} + /> + + + {todo.text} + + + handleDeleteTodo(todo.id)} + className="p-2 rounded-lg active:opacity-70" + > + + + + + ))} + + )} + {{/if}} + + + ); +} \ No newline at end of file diff --git a/apps/cli/templates/extras/bunfig.toml.hbs b/apps/cli/templates/extras/bunfig.toml.hbs index 32357654a..8348bd193 100644 --- a/apps/cli/templates/extras/bunfig.toml.hbs +++ b/apps/cli/templates/extras/bunfig.toml.hbs @@ -1,6 +1,6 @@ [install] -{{#if (or (includes frontend "nuxt") (includes frontend "native-nativewind") (includes frontend "native-unistyles"))}} -linker = "hoisted" # having issues with Nuxt and NativeWind/Unistyles when linker is isolated +{{#if (or (includes frontend "nuxt"))}} +linker = "hoisted" # having issues with Nuxt when linker is isolated {{else}} linker = "isolated" -{{/if}} +{{/if}} \ No newline at end of file diff --git a/apps/cli/templates/frontend/native/bare/_gitignore b/apps/cli/templates/frontend/native/bare/_gitignore new file mode 100644 index 000000000..fc7bb369b --- /dev/null +++ b/apps/cli/templates/frontend/native/bare/_gitignore @@ -0,0 +1,18 @@ +node_modules/ +.expo/ +dist/ +npm-debug.* +*.jks +*.p8 +*.p12 +*.key +*.mobileprovision +*.orig.* +web-build/ + +# macOS +.DS_Store + +# Temporary files created by Metro to check the health of the file watcher +.metro-health-check* + diff --git a/apps/cli/templates/frontend/native/nativewind/app.json.hbs b/apps/cli/templates/frontend/native/bare/app.json.hbs similarity index 99% rename from apps/cli/templates/frontend/native/nativewind/app.json.hbs rename to apps/cli/templates/frontend/native/bare/app.json.hbs index 86a6b10ab..21d0859d2 100644 --- a/apps/cli/templates/frontend/native/nativewind/app.json.hbs +++ b/apps/cli/templates/frontend/native/bare/app.json.hbs @@ -47,3 +47,4 @@ } } } + diff --git a/apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/_layout.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/_layout.tsx.hbs similarity index 54% rename from apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/_layout.tsx.hbs rename to apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/_layout.tsx.hbs index a1a1f04ad..0c479f681 100644 --- a/apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/_layout.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/_layout.tsx.hbs @@ -1,46 +1,41 @@ import { TabBarIcon } from "@/components/tabbar-icon"; import { useColorScheme } from "@/lib/use-color-scheme"; import { Tabs } from "expo-router"; +import { NAV_THEME } from "@/lib/constants"; export default function TabLayout() { const { isDarkColorScheme } = useColorScheme(); + const theme = isDarkColorScheme ? NAV_THEME.dark : NAV_THEME.light; return ( , - }} + }}} /> ( ), - }} + }}} /> ); } + diff --git a/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/index.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/index.tsx.hbs new file mode 100644 index 000000000..f5595b919 --- /dev/null +++ b/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/index.tsx.hbs @@ -0,0 +1,43 @@ +import { Container } from "@/components/container"; +import { ScrollView, Text, View, StyleSheet } from "react-native"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { NAV_THEME } from "@/lib/constants"; + +export default function TabOne() { + const { colorScheme } = useColorScheme(); + const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; + + return ( + + + + + Tab One + + + Explore the first section of your app + + + + + ); +} + +const styles = StyleSheet.create({ + scrollView: { + flex: 1, + padding: 24, + }, + content: { + paddingVertical: 32, + }, + title: { + fontSize: 30, + fontWeight: "bold", + marginBottom: 8, + }, + subtitle: { + fontSize: 18, + }, +}); + diff --git a/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/two.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/two.tsx.hbs new file mode 100644 index 000000000..ad9642384 --- /dev/null +++ b/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/two.tsx.hbs @@ -0,0 +1,43 @@ +import { Container } from "@/components/container"; +import { ScrollView, Text, View, StyleSheet } from "react-native"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { NAV_THEME } from "@/lib/constants"; + +export default function TabTwo() { + const { colorScheme } = useColorScheme(); + const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; + + return ( + + + + + Tab Two + + + Discover more features and content + + + + + ); +} + +const styles = StyleSheet.create({ + scrollView: { + flex: 1, + padding: 24, + }, + content: { + paddingVertical: 32, + }, + title: { + fontSize: 30, + fontWeight: "bold", + marginBottom: 8, + }, + subtitle: { + fontSize: 18, + }, +}); + diff --git a/apps/cli/templates/frontend/native/nativewind/app/(drawer)/_layout.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/(drawer)/_layout.tsx.hbs similarity index 66% rename from apps/cli/templates/frontend/native/nativewind/app/(drawer)/_layout.tsx.hbs rename to apps/cli/templates/frontend/native/bare/app/(drawer)/_layout.tsx.hbs index a643ad063..98374fb4c 100644 --- a/apps/cli/templates/frontend/native/nativewind/app/(drawer)/_layout.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/app/(drawer)/_layout.tsx.hbs @@ -1,25 +1,47 @@ import { Ionicons, MaterialIcons } from "@expo/vector-icons"; import { Link } from "expo-router"; import { Drawer } from "expo-router/drawer"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { NAV_THEME } from "@/lib/constants"; import { HeaderButton } from "@/components/header-button"; const DrawerLayout = () => { + const { colorScheme } = useColorScheme(); + const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; + return ( - + ( ), - }} + }}} /> ( @@ -30,24 +52,24 @@ const DrawerLayout = () => { ), - }} + }}} /> {{#if (includes examples "todo")}} ( ), - }} + }}} /> {{/if}} {{#if (includes examples "ai")}} ( @@ -57,7 +79,7 @@ const DrawerLayout = () => { color={color} /> ), - }} + }}} /> {{/if}} @@ -65,3 +87,4 @@ const DrawerLayout = () => { }; export default DrawerLayout; + diff --git a/apps/cli/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs new file mode 100644 index 000000000..44b4094a3 --- /dev/null +++ b/apps/cli/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs @@ -0,0 +1,249 @@ +import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from "react-native"; +import { Container } from "@/components/container"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { NAV_THEME } from "@/lib/constants"; +{{#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 (and (eq backend "convex") (eq auth "clerk"))}} +import { Link } from "expo-router"; +import { Authenticated, AuthLoading, Unauthenticated, useQuery } from "convex/react"; +import { api } from "@{{ projectName }}/backend/convex/_generated/api"; +import { useUser } from "@clerk/clerk-expo"; +import { SignOutButton } from "@/components/sign-out-button"; +{{else if (and (eq backend "convex") (eq auth "better-auth"))}} +import { useConvexAuth, useQuery } from "convex/react"; +import { api } from "@{{ projectName }}/backend/convex/_generated/api"; +import { authClient } from "@/lib/auth-client"; +import { SignIn } from "@/components/sign-in"; +import { SignUp } from "@/components/sign-up"; +{{else if (eq backend "convex")}} +import { useQuery } from "convex/react"; +import { api } from "@{{ projectName }}/backend/convex/_generated/api"; +{{/if}} + +export default function Home() { + const { colorScheme } = useColorScheme(); + const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; + {{#if (eq api "orpc")}} + const healthCheck = useQuery(orpc.healthCheck.queryOptions()); + {{/if}} + {{#if (eq api "trpc")}} + const healthCheck = useQuery(trpc.healthCheck.queryOptions()); + {{/if}} + {{#if (and (eq backend "convex") (eq auth "clerk"))}} + const { user } = useUser(); + const healthCheck = useQuery(api.healthCheck.get); + const privateData = useQuery(api.privateData.get); + {{else if (and (eq backend "convex") (eq auth "better-auth"))}} + const healthCheck = useQuery(api.healthCheck.get); + const { isAuthenticated } = useConvexAuth(); + const user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : "skip"); + {{else if (eq backend "convex")}} + const healthCheck = useQuery(api.healthCheck.get); + {{/if}} + + return ( + + + + + BETTER T STACK + + + {{#unless (and (eq backend "convex") (eq auth "better-auth"))}} + + {{#if (eq backend "convex")}} + + + + + Convex + + + {healthCheck === undefined + ? "Checking..." + : healthCheck === "OK" + ? "Connected to API" + : "API Disconnected"} + + + + {{else}} + {{#unless (eq api "none")}} + + + + + {{#if (eq api "orpc")}}ORPC{{else}}TRPC{{/if}} + + + {healthCheck.isLoading + ? "Checking connection..." + : healthCheck.data + ? "All systems operational" + : "Service unavailable"} + + + + {{/unless}} + {{/if}} + + {{/unless}} + + {{#if (and (eq backend "convex") (eq auth "clerk"))}} + + Hello {user?.emailAddresses[0].emailAddress} + Private Data: {privateData?.message} + + + + + Sign in + + + Sign up + + + + Loading... + + {{/if}} + + {{#if (and (eq backend "convex") (eq auth "better-auth"))}} + {user ? ( + + + + Welcome, {user.name} + + + + {user.email} + + { + authClient.signOut(); + }} + > + Sign Out + + + ) : null} + + + API Status + + + + + {healthCheck === undefined + ? "Checking..." + : healthCheck === "OK" + ? "Connected to API" + : "API Disconnected"} + + + + {!user && ( + <> + + + + )} + {{/if}} + + + + ); +} + +const styles = StyleSheet.create({ + scrollView: { + flex: 1, + }, + content: { + padding: 16, + }, + title: { + fontFamily: "monospace", + fontSize: 32, + fontWeight: "bold", + marginBottom: 16, + }, + card: { + borderWidth: 1, + borderRadius: 12, + padding: 24, + marginBottom: 24, + }, + statusRow: { + flexDirection: "row", + alignItems: "center", + gap: 12, + }, + statusIndicator: { + height: 12, + width: 12, + borderRadius: 6, + }, + statusContent: { + flex: 1, + }, + statusTitle: { + fontSize: 14, + fontWeight: "500", + }, + statusText: { + fontSize: 12, + }, + userCard: { + marginBottom: 24, + padding: 16, + borderRadius: 8, + borderWidth: 1, + }, + userHeader: { + flexDirection: "row", + justifyContent: "space-between", + alignItems: "center", + marginBottom: 8, + }, + userText: { + fontSize: 16, + }, + userName: { + fontWeight: "500", + }, + userEmail: { + fontSize: 14, + marginBottom: 16, + }, + signOutButton: { + paddingVertical: 8, + paddingHorizontal: 16, + borderRadius: 6, + alignSelf: "flex-start", + }, + signOutText: { + color: "#ffffff", + fontWeight: "500", + }, + statusCard: { + marginBottom: 24, + borderRadius: 8, + borderWidth: 1, + padding: 16, + }, + statusCardTitle: { + marginBottom: 12, + fontWeight: "500", + }, +}); + diff --git a/apps/cli/templates/frontend/native/bare/app/+not-found.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/+not-found.tsx.hbs new file mode 100644 index 000000000..aa742386c --- /dev/null +++ b/apps/cli/templates/frontend/native/bare/app/+not-found.tsx.hbs @@ -0,0 +1,69 @@ +import { Container } from "@/components/container"; +import { Link, Stack } from "expo-router"; +import { Text, View, StyleSheet } from "react-native"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { NAV_THEME } from "@/lib/constants"; + +export default function NotFoundScreen() { + const { colorScheme } = useColorScheme(); + const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; + + return ( + <> + + + + + 🤔 + + Page Not Found + + + Sorry, the page you're looking for doesn't exist. + + + + Go to Home + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + justifyContent: "center", + alignItems: "center", + padding: 24, + }, + content: { + alignItems: "center", + }, + emoji: { + fontSize: 64, + marginBottom: 16, + }, + title: { + fontSize: 24, + fontWeight: "bold", + marginBottom: 8, + textAlign: "center", + }, + subtitle: { + fontSize: 16, + textAlign: "center", + marginBottom: 32, + maxWidth: 300, + }, + link: { + fontWeight: "500", + paddingHorizontal: 24, + paddingVertical: 12, + borderRadius: 8, + }, +}); + diff --git a/apps/cli/templates/frontend/native/bare/app/_layout.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/_layout.tsx.hbs new file mode 100644 index 000000000..1f49b24b5 --- /dev/null +++ b/apps/cli/templates/frontend/native/bare/app/_layout.tsx.hbs @@ -0,0 +1,159 @@ +{{#if (includes examples "ai")}} +import "@/polyfills"; +{{/if}} +{{#if (eq backend "convex")}} +{{#if (eq auth "better-auth")}} +import { ConvexReactClient } from "convex/react"; +import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react"; +import { authClient } from "@/lib/auth-client"; +{{else}} +import { ConvexProvider, ConvexReactClient } from "convex/react"; +{{/if}} +{{#if (eq auth "clerk")}} +import { ClerkProvider, useAuth } from "@clerk/clerk-expo"; +import { ConvexProviderWithClerk } from "convex/react-clerk"; +import { tokenCache } from "@clerk/clerk-expo/token-cache"; +{{/if}} +{{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"; +{{#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, StyleSheet } 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; +} + +setAndroidNavigationBar(colorScheme); +setIsColorSchemeLoaded(true); +hasMounted.current = true; +}, []); + +if (!isColorSchemeLoaded) { +return null; +} +return ( +{{#if (eq backend "convex")}} +{{#if (eq auth "clerk")}} + + + + + + + + + + + + + + +{{else if (eq auth "better-auth")}} + + + + + + + + + + + +{{else}} + + + + + + + + + + + +{{/if}} +{{else}} +{{#unless (eq api "none")}} + + + + + + + + + + + +{{else}} + + + + + + + + + +{{/unless}} +{{/if}} +); +} + +const useIsomorphicLayoutEffect = +Platform.OS === "web" && typeof window === "undefined" +? React.useEffect +: React.useLayoutEffect; + +const styles = StyleSheet.create({ +container: { +flex: 1, +}, +}); \ No newline at end of file diff --git a/apps/cli/templates/frontend/native/bare/app/modal.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/modal.tsx.hbs new file mode 100644 index 000000000..601eb7588 --- /dev/null +++ b/apps/cli/templates/frontend/native/bare/app/modal.tsx.hbs @@ -0,0 +1,37 @@ +import { Container } from "@/components/container"; +import { Text, View, StyleSheet } from "react-native"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { NAV_THEME } from "@/lib/constants"; + +export default function Modal() { + const { colorScheme } = useColorScheme(); + const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; + + return ( + + + + Modal + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + padding: 24, + }, + header: { + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + marginBottom: 32, + }, + title: { + fontSize: 24, + fontWeight: "bold", + }, +}); + diff --git a/apps/cli/templates/frontend/native/nativewind/babel.config.js.hbs b/apps/cli/templates/frontend/native/bare/babel.config.js.hbs similarity index 58% rename from apps/cli/templates/frontend/native/nativewind/babel.config.js.hbs rename to apps/cli/templates/frontend/native/bare/babel.config.js.hbs index d54d5e71b..5e34abbcc 100644 --- a/apps/cli/templates/frontend/native/nativewind/babel.config.js.hbs +++ b/apps/cli/templates/frontend/native/bare/babel.config.js.hbs @@ -5,10 +5,8 @@ module.exports = (api) => { plugins.push("react-native-worklets/plugin"); return { - presets: [ - ["babel-preset-expo", { jsxImportSource: "nativewind" }], - "nativewind/babel", - ], + presets: ["babel-preset-expo"], plugins, }; }; + diff --git a/apps/cli/templates/frontend/native/bare/components/container.tsx.hbs b/apps/cli/templates/frontend/native/bare/components/container.tsx.hbs new file mode 100644 index 000000000..cd9a9da7c --- /dev/null +++ b/apps/cli/templates/frontend/native/bare/components/container.tsx.hbs @@ -0,0 +1,24 @@ +import React from "react"; +import { SafeAreaView, StyleSheet } from "react-native-safe-area-context"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { NAV_THEME } from "@/lib/constants"; + +export function Container({ children }: { children: React.ReactNode }) { + const { colorScheme } = useColorScheme(); + const backgroundColor = colorScheme === "dark" + ? NAV_THEME.dark.background + : NAV_THEME.light.background; + + return ( + + {children} + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, +}); + diff --git a/apps/cli/templates/frontend/native/bare/components/header-button.tsx.hbs b/apps/cli/templates/frontend/native/bare/components/header-button.tsx.hbs new file mode 100644 index 000000000..d703d1923 --- /dev/null +++ b/apps/cli/templates/frontend/native/bare/components/header-button.tsx.hbs @@ -0,0 +1,48 @@ +import FontAwesome from "@expo/vector-icons/FontAwesome"; +import { forwardRef } from "react"; +import { Pressable, StyleSheet } from "react-native"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { NAV_THEME } from "@/lib/constants"; + +export const HeaderButton = forwardRef< + typeof Pressable, + { onPress?: () => void } +>(({ onPress }, ref) => { + const { colorScheme } = useColorScheme(); + const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; + + return ( + [ + styles.button, + { + backgroundColor: pressed + ? theme.secondary + : theme.card, + }, + ]} + > + {({ pressed }) => ( + + )} + + ); +}); + +const styles = StyleSheet.create({ + button: { + padding: 8, + marginRight: 8, + borderRadius: 8, + }, +}); + diff --git a/apps/cli/templates/frontend/native/nativewind/components/tabbar-icon.tsx.hbs b/apps/cli/templates/frontend/native/bare/components/tabbar-icon.tsx.hbs similarity index 99% rename from apps/cli/templates/frontend/native/nativewind/components/tabbar-icon.tsx.hbs rename to apps/cli/templates/frontend/native/bare/components/tabbar-icon.tsx.hbs index ecf1944c1..4f83b65e2 100644 --- a/apps/cli/templates/frontend/native/nativewind/components/tabbar-icon.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/components/tabbar-icon.tsx.hbs @@ -6,3 +6,4 @@ export const TabBarIcon = (props: { }) => { return ; }; + diff --git a/apps/cli/templates/frontend/native/nativewind/lib/android-navigation-bar.tsx.hbs b/apps/cli/templates/frontend/native/bare/lib/android-navigation-bar.tsx.hbs similarity index 99% rename from apps/cli/templates/frontend/native/nativewind/lib/android-navigation-bar.tsx.hbs rename to apps/cli/templates/frontend/native/bare/lib/android-navigation-bar.tsx.hbs index 47d6134d8..c7861506c 100644 --- a/apps/cli/templates/frontend/native/nativewind/lib/android-navigation-bar.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/lib/android-navigation-bar.tsx.hbs @@ -9,3 +9,4 @@ export async function setAndroidNavigationBar(theme: "light" | "dark") { theme === "dark" ? NAV_THEME.dark.background : NAV_THEME.light.background, ); } + diff --git a/apps/cli/templates/frontend/native/nativewind/lib/constants.ts.hbs b/apps/cli/templates/frontend/native/bare/lib/constants.ts.hbs similarity index 99% rename from apps/cli/templates/frontend/native/nativewind/lib/constants.ts.hbs rename to apps/cli/templates/frontend/native/bare/lib/constants.ts.hbs index 8e7ca1180..73792a692 100644 --- a/apps/cli/templates/frontend/native/nativewind/lib/constants.ts.hbs +++ b/apps/cli/templates/frontend/native/bare/lib/constants.ts.hbs @@ -16,3 +16,4 @@ export const NAV_THEME = { text: "hsl(210 40% 98%)", }, }; + diff --git a/apps/cli/templates/frontend/native/bare/lib/use-color-scheme.ts.hbs b/apps/cli/templates/frontend/native/bare/lib/use-color-scheme.ts.hbs new file mode 100644 index 000000000..c5b14a900 --- /dev/null +++ b/apps/cli/templates/frontend/native/bare/lib/use-color-scheme.ts.hbs @@ -0,0 +1,20 @@ +import { useColorScheme as useRNColorScheme } from "react-native"; + +export function useColorScheme() { + const systemColorScheme = useRNColorScheme(); + const colorScheme = systemColorScheme ?? "light"; + + return { + colorScheme: colorScheme as "light" | "dark", + isDarkColorScheme: colorScheme === "dark", + setColorScheme: () => { + // Color scheme is managed by the system in bare mode + console.warn("setColorScheme is not available in bare mode. Color scheme is managed by the system."); + }, + toggleColorScheme: () => { + // Color scheme is managed by the system in bare mode + console.warn("toggleColorScheme is not available in bare mode. Color scheme is managed by the system."); + }, + }; +} + diff --git a/apps/cli/templates/frontend/native/bare/metro.config.js.hbs b/apps/cli/templates/frontend/native/bare/metro.config.js.hbs new file mode 100644 index 000000000..81e7c2c7b --- /dev/null +++ b/apps/cli/templates/frontend/native/bare/metro.config.js.hbs @@ -0,0 +1,9 @@ +// Learn more https://docs.expo.io/guides/customizing-metro +const { getDefaultConfig } = require("expo/metro-config"); + +const config = getDefaultConfig(__dirname); + +config.resolver.unstable_enablePackageExports = true; + +module.exports = config; + diff --git a/apps/cli/templates/frontend/native/nativewind/package.json.hbs b/apps/cli/templates/frontend/native/bare/package.json.hbs similarity index 96% rename from apps/cli/templates/frontend/native/nativewind/package.json.hbs rename to apps/cli/templates/frontend/native/bare/package.json.hbs index ad3c76773..b70273f83 100644 --- a/apps/cli/templates/frontend/native/nativewind/package.json.hbs +++ b/apps/cli/templates/frontend/native/bare/package.json.hbs @@ -31,7 +31,6 @@ "expo-status-bar": "~3.0.7", "expo-system-ui": "~6.0.7", "expo-web-browser": "~15.0.6", - "nativewind": "^4.1.23", "react": "19.1.0", "react-dom": "19.1.0", "react-native": "0.81.4", @@ -45,8 +44,8 @@ "devDependencies": { "@babel/core": "^7.26.10", "@types/react": "~19.1.10", - "tailwindcss": "^3.4.17", "typescript": "~5.8.2" }, "private": true } + diff --git a/apps/cli/templates/frontend/native/bare/tsconfig.json.hbs b/apps/cli/templates/frontend/native/bare/tsconfig.json.hbs new file mode 100644 index 000000000..5fdd74bf7 --- /dev/null +++ b/apps/cli/templates/frontend/native/bare/tsconfig.json.hbs @@ -0,0 +1,11 @@ +{ + "extends": "expo/tsconfig.base", + "compilerOptions": { + "strict": true, + "paths": { + "@/*": ["./*"] + } + }, + "include": ["**/*.ts", "**/*.tsx", ".expo/types/**/*.ts", "expo-env.d.ts"] +} + diff --git a/apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/index.tsx.hbs b/apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/index.tsx.hbs deleted file mode 100644 index 24a051215..000000000 --- a/apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/index.tsx.hbs +++ /dev/null @@ -1,19 +0,0 @@ -import { Container } from "@/components/container"; -import { ScrollView, Text, View } from "react-native"; - -export default function TabOne() { - return ( - - - - - Tab One - - - Explore the first section of your app - - - - - ); -} diff --git a/apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/two.tsx.hbs b/apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/two.tsx.hbs deleted file mode 100644 index 1736606a3..000000000 --- a/apps/cli/templates/frontend/native/nativewind/app/(drawer)/(tabs)/two.tsx.hbs +++ /dev/null @@ -1,19 +0,0 @@ -import { Container } from "@/components/container"; -import { ScrollView, Text, View } from "react-native"; - -export default function TabTwo() { - return ( - - - - - Tab Two - - - Discover more features and content - - - - - ); -} diff --git a/apps/cli/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs b/apps/cli/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs deleted file mode 100644 index 99ca6ca2e..000000000 --- a/apps/cli/templates/frontend/native/nativewind/app/(drawer)/index.tsx.hbs +++ /dev/null @@ -1,178 +0,0 @@ -import { View, Text, ScrollView, TouchableOpacity } 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 (and (eq backend "convex") (eq auth "clerk"))}} -import { Link } from "expo-router"; -import { Authenticated, AuthLoading, Unauthenticated, useQuery } from "convex/react"; -import { api } from "@{{ projectName }}/backend/convex/_generated/api"; -import { useUser } from "@clerk/clerk-expo"; -import { SignOutButton } from "@/components/sign-out-button"; -{{else if (and (eq backend "convex") (eq auth "better-auth"))}} -import { useConvexAuth, useQuery } from "convex/react"; -import { api } from "@{{ projectName }}/backend/convex/_generated/api"; -import { authClient } from "@/lib/auth-client"; -import { SignIn } from "@/components/sign-in"; -import { SignUp } from "@/components/sign-up"; -{{else 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 (and (eq backend "convex") (eq auth "clerk"))}} - const { user } = useUser(); - const healthCheck = useQuery(api.healthCheck.get); - const privateData = useQuery(api.privateData.get); - {{else if (and (eq backend "convex") (eq auth "better-auth"))}} - const healthCheck = useQuery(api.healthCheck.get); - const { isAuthenticated } = useConvexAuth(); - const user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : "skip"); - {{else if (eq backend "convex")}} - const healthCheck = useQuery(api.healthCheck.get); - {{/if}} - - return ( - - - - - BETTER T STACK - - - {{#unless (and (eq backend "convex") (eq auth "better-auth"))}} - - {{#if (eq backend "convex")}} - - - - - Convex - - - { - healthCheck === undefined - ? "Checking..." - : healthCheck === "OK" - ? "Connected to API" - : "API Disconnected" - } - - - - {{else}} - {{#unless (eq api "none")}} - - - - - {{#if (eq api "orpc")}}ORPC{{else}}TRPC{{/if}} - - - {healthCheck.isLoading - ? "Checking connection..." - : healthCheck.data - ? "All systems operational" - : "Service unavailable"} - - - - {{/unless}} - {{/if}} - - {{/unless}} - - {{#if (and (eq backend "convex") (eq auth "clerk"))}} - - Hello {user?.emailAddresses[0].emailAddress} - Private Data: {privateData?.message} - - - - - Sign in - - - Sign up - - - - Loading... - - {{/if}} - - {{#if (and (eq backend "convex") (eq auth "better-auth"))}} - {user ? ( - - - - Welcome,{" "} - {user.name} - - - - {user.email} - - { - authClient.signOut(); - }} - > - Sign Out - - - ) : null} - - - API Status - - - - - { - healthCheck === undefined - ? "Checking..." - : healthCheck === "OK" - ? "Connected to API" - : "API Disconnected" - } - - - - {!user && ( - <> - - - - )} - {{/if}} - - - - ); -} \ No newline at end of file diff --git a/apps/cli/templates/frontend/native/nativewind/app/+not-found.tsx.hbs b/apps/cli/templates/frontend/native/nativewind/app/+not-found.tsx.hbs deleted file mode 100644 index 8a9f34000..000000000 --- a/apps/cli/templates/frontend/native/nativewind/app/+not-found.tsx.hbs +++ /dev/null @@ -1,29 +0,0 @@ -import { Container } from "@/components/container"; -import { Link, Stack } from "expo-router"; -import { Text, View } from "react-native"; - -export default function NotFoundScreen() { - return ( - <> - - - - - 🤔 - - Page Not Found - - - Sorry, the page you're looking for doesn't exist. - - - - Go to Home - - - - - - - ); -} diff --git a/apps/cli/templates/frontend/native/nativewind/app/_layout.tsx.hbs b/apps/cli/templates/frontend/native/nativewind/app/_layout.tsx.hbs deleted file mode 100644 index 64ac0ccbe..000000000 --- a/apps/cli/templates/frontend/native/nativewind/app/_layout.tsx.hbs +++ /dev/null @@ -1,175 +0,0 @@ -{{#if (includes examples "ai")}} -import "@/polyfills"; -{{/if}} -{{#if (eq backend "convex")}} -{{#if (eq auth "better-auth")}} -import { ConvexReactClient } from "convex/react"; -import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react"; -import { authClient } from "@/lib/auth-client"; -{{else}} -import { ConvexProvider, ConvexReactClient } from "convex/react"; -{{/if}} -{{#if (eq auth "clerk")}} -import { ClerkProvider, useAuth } from "@clerk/clerk-expo"; -import { ConvexProviderWithClerk } from "convex/react-clerk"; -import { tokenCache } from "@clerk/clerk-expo/token-cache"; -{{/if}} -{{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")}} - {{#if (eq auth "clerk")}} - - - - - - - - - - - - - - - {{else if (eq auth "better-auth")}} - - - - - - - - - - - - {{else}} - - - - - - - - - - - - {{/if}} - {{else}} - {{#unless (eq api "none")}} - - - - - - - - - - - - {{else}} - - - - - - - - - - {{/unless}} - {{/if}} - ); -} - -const useIsomorphicLayoutEffect = - Platform.OS === "web" && typeof window === "undefined" - ? React.useEffect - : React.useLayoutEffect; diff --git a/apps/cli/templates/frontend/native/nativewind/app/modal.tsx.hbs b/apps/cli/templates/frontend/native/nativewind/app/modal.tsx.hbs deleted file mode 100644 index 7fdcaea8b..000000000 --- a/apps/cli/templates/frontend/native/nativewind/app/modal.tsx.hbs +++ /dev/null @@ -1,14 +0,0 @@ -import { Container } from "@/components/container"; -import { Text, View } from "react-native"; - -export default function Modal() { - return ( - - - - Modal - - - - ); -} diff --git a/apps/cli/templates/frontend/native/nativewind/components/container.tsx.hbs b/apps/cli/templates/frontend/native/nativewind/components/container.tsx.hbs deleted file mode 100644 index bf824f34c..000000000 --- a/apps/cli/templates/frontend/native/nativewind/components/container.tsx.hbs +++ /dev/null @@ -1,8 +0,0 @@ -import React from "react"; -import { SafeAreaView } from "react-native-safe-area-context"; - -export const Container = ({ children }: { children: React.ReactNode }) => { - return ( - {children} - ); -}; diff --git a/apps/cli/templates/frontend/native/nativewind/components/header-button.tsx.hbs b/apps/cli/templates/frontend/native/nativewind/components/header-button.tsx.hbs deleted file mode 100644 index bb3a21ad9..000000000 --- a/apps/cli/templates/frontend/native/nativewind/components/header-button.tsx.hbs +++ /dev/null @@ -1,26 +0,0 @@ -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 ( - - {({ pressed }) => ( - - )} - - ); -}); diff --git a/apps/cli/templates/frontend/native/nativewind/global.css b/apps/cli/templates/frontend/native/nativewind/global.css deleted file mode 100644 index f658ca1d8..000000000 --- a/apps/cli/templates/frontend/native/nativewind/global.css +++ /dev/null @@ -1,50 +0,0 @@ -@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%; - } -} diff --git a/apps/cli/templates/frontend/native/nativewind/lib/use-color-scheme.ts.hbs b/apps/cli/templates/frontend/native/nativewind/lib/use-color-scheme.ts.hbs deleted file mode 100644 index df75499f1..000000000 --- a/apps/cli/templates/frontend/native/nativewind/lib/use-color-scheme.ts.hbs +++ /dev/null @@ -1,12 +0,0 @@ -import { useColorScheme as useNativewindColorScheme } from "nativewind"; - -export function useColorScheme() { - const { colorScheme, setColorScheme, toggleColorScheme } = - useNativewindColorScheme(); - return { - colorScheme: colorScheme ?? "dark", - isDarkColorScheme: colorScheme === "dark", - setColorScheme, - toggleColorScheme, - }; -} diff --git a/apps/cli/templates/frontend/native/nativewind/metro.config.js.hbs b/apps/cli/templates/frontend/native/nativewind/metro.config.js.hbs deleted file mode 100644 index 55fe4a81a..000000000 --- a/apps/cli/templates/frontend/native/nativewind/metro.config.js.hbs +++ /dev/null @@ -1,12 +0,0 @@ -// Learn more https://docs.expo.io/guides/customizing-metro -const { getDefaultConfig } = require("expo/metro-config"); -const { withNativeWind } = require("nativewind/metro"); - -const config = withNativeWind(getDefaultConfig(__dirname), { - input: "./global.css", - configPath: "./tailwind.config.js", -}); - -config.resolver.unstable_enablePackageExports = true; - -module.exports = config; \ No newline at end of file diff --git a/apps/cli/templates/frontend/native/nativewind/tailwind.config.js.hbs b/apps/cli/templates/frontend/native/nativewind/tailwind.config.js.hbs deleted file mode 100644 index b6ea71147..000000000 --- a/apps/cli/templates/frontend/native/nativewind/tailwind.config.js.hbs +++ /dev/null @@ -1,59 +0,0 @@ -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 = []; diff --git a/apps/cli/templates/frontend/native/nativewind/_gitignore b/apps/cli/templates/frontend/native/uniwind/_gitignore similarity index 80% rename from apps/cli/templates/frontend/native/nativewind/_gitignore rename to apps/cli/templates/frontend/native/uniwind/_gitignore index 829dfca22..b1b034d82 100644 --- a/apps/cli/templates/frontend/native/nativewind/_gitignore +++ b/apps/cli/templates/frontend/native/uniwind/_gitignore @@ -9,17 +9,13 @@ npm-debug.* *.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* + +# UniWind generated types +uniwind-types.d.ts + diff --git a/apps/cli/templates/frontend/native/uniwind/app.json.hbs b/apps/cli/templates/frontend/native/uniwind/app.json.hbs new file mode 100644 index 000000000..013c10136 --- /dev/null +++ b/apps/cli/templates/frontend/native/uniwind/app.json.hbs @@ -0,0 +1,16 @@ +{ + "expo": { + "scheme": "{{projectName}}", + "userInterfaceStyle": "automatic", + "orientation": "default", + "web": { + "bundler": "metro" + }, + "name": "{{projectName}}", + "slug": "{{projectName}}", + "plugins": [ + "expo-font" + ] + } +} + diff --git a/apps/cli/templates/frontend/native/uniwind/app/(drawer)/(tabs)/_layout.tsx.hbs b/apps/cli/templates/frontend/native/uniwind/app/(drawer)/(tabs)/_layout.tsx.hbs new file mode 100644 index 000000000..3c6918435 --- /dev/null +++ b/apps/cli/templates/frontend/native/uniwind/app/(drawer)/(tabs)/_layout.tsx.hbs @@ -0,0 +1,46 @@ +import { Tabs } from "expo-router"; +import { Ionicons } from "@expo/vector-icons"; +import { useThemeColor } from "heroui-native"; + +export default function TabLayout() { + const themeColorForeground = useThemeColor("foreground"); + const themeColorBackground = useThemeColor("background"); + + return ( + + ( + + ), + }} + /> + ( + + ), + }} + /> + + ); +} diff --git a/apps/cli/templates/frontend/native/uniwind/app/(drawer)/(tabs)/index.tsx.hbs b/apps/cli/templates/frontend/native/uniwind/app/(drawer)/(tabs)/index.tsx.hbs new file mode 100644 index 000000000..e092affe3 --- /dev/null +++ b/apps/cli/templates/frontend/native/uniwind/app/(drawer)/(tabs)/index.tsx.hbs @@ -0,0 +1,15 @@ +import { Container } from "@/components/container"; +import { Text, View } from "react-native"; +import { Card } from "heroui-native"; + +export default function Home() { + return ( + + + + Tab One + + + + ); +} diff --git a/apps/cli/templates/frontend/native/uniwind/app/(drawer)/(tabs)/two.tsx.hbs b/apps/cli/templates/frontend/native/uniwind/app/(drawer)/(tabs)/two.tsx.hbs new file mode 100644 index 000000000..01bc323f1 --- /dev/null +++ b/apps/cli/templates/frontend/native/uniwind/app/(drawer)/(tabs)/two.tsx.hbs @@ -0,0 +1,15 @@ +import { Container } from "@/components/container"; +import { Text, View } from "react-native"; +import { Card } from "heroui-native"; + +export default function TabTwo() { + return ( + + + + TabTwo + + + + ); +} diff --git a/apps/cli/templates/frontend/native/uniwind/app/(drawer)/_layout.tsx.hbs b/apps/cli/templates/frontend/native/uniwind/app/(drawer)/_layout.tsx.hbs new file mode 100644 index 000000000..2a764bad1 --- /dev/null +++ b/apps/cli/templates/frontend/native/uniwind/app/(drawer)/_layout.tsx.hbs @@ -0,0 +1,83 @@ +import React, { useCallback } from "react"; +import { Ionicons, MaterialIcons } from "@expo/vector-icons"; +import { Link } from "expo-router"; +import { Drawer } from "expo-router/drawer"; +import { useThemeColor } from "heroui-native"; +import { Pressable } from "react-native"; +import { ThemeToggle } from "@/components/theme-toggle"; + +function DrawerLayout() { + const themeColorForeground = useThemeColor("foreground"); + const themeColorBackground = useThemeColor("background"); + + const renderThemeToggle = useCallback(() => , []); + + return ( + + ( + + ), + }} + /> + ( + + ), + headerRight: () => ( + + + + + + ), + }} + /> + {{#if (includes examples "todo")}} + ( + + ), + }} + /> + {{/if}} + {{#if (includes examples "ai")}} + ( + + ), + }} + /> + {{/if}} + + ); +} + +export default DrawerLayout; \ No newline at end of file diff --git a/apps/cli/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs b/apps/cli/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs new file mode 100644 index 000000000..ac5ed0411 --- /dev/null +++ b/apps/cli/templates/frontend/native/uniwind/app/(drawer)/index.tsx.hbs @@ -0,0 +1,151 @@ +import { Text, View } 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 (and (eq backend "convex") (eq auth "clerk"))}} +import { Link } from "expo-router"; +import { Authenticated, AuthLoading, Unauthenticated, useQuery } from "convex/react"; +import { api } from "@{{projectName}}/backend/convex/_generated/api"; +import { useUser } from "@clerk/clerk-expo"; +import { SignOutButton } from "@/components/sign-out-button"; +{{else if (and (eq backend "convex") (eq auth "better-auth"))}} +import { useConvexAuth, useQuery } from "convex/react"; +import { api } from "@{{projectName}}/backend/convex/_generated/api"; +import { authClient } from "@/lib/auth-client"; +import { SignIn } from "@/components/sign-in"; +import { SignUp } from "@/components/sign-up"; +{{else if (eq backend "convex")}} +import { useQuery } from "convex/react"; +import { api } from "@{{projectName}}/backend/convex/_generated/api"; +{{/if}} +import { Ionicons } from "@expo/vector-icons"; +import { Card, Chip, useThemeColor } from "heroui-native"; + +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 (and (eq backend "convex") (eq auth "clerk"))}} + const { user } = useUser(); + const healthCheck = useQuery(api.healthCheck.get); + const privateData = useQuery(api.privateData.get); +{{else if (and (eq backend "convex") (eq auth "better-auth"))}} + const healthCheck = useQuery(api.healthCheck.get); + const { isAuthenticated } = useConvexAuth(); + const user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : "skip"); +{{else if (eq backend "convex")}} + const healthCheck = useQuery(api.healthCheck.get); +{{/if}} + const mutedColor = useThemeColor("muted"); + const successColor = useThemeColor("success"); + const dangerColor = useThemeColor("danger"); + +{{#if (eq backend "convex")}} + const isConnected = healthCheck === "OK"; + const isLoading = healthCheck === undefined; +{{else}} +{{#unless (eq api "none")}} + const isConnected = healthCheck?.data === "OK"; + const isLoading = healthCheck?.isLoading; +{{/unless}} +{{/if}} + + return ( + + + + BETTER T STACK + + + + {{#unless (and (eq backend "convex") (eq auth "better-auth"))}} + + + System Status + + + {isConnected ? "LIVE" : "OFFLINE"} + + + + + + + + + {{#if (eq backend "convex")}} + Convex Backend + {{else}} + {{#unless (eq api "none")}} + {{#if (eq api "orpc")}}ORPC{{else}}TRPC{{/if}} Backend + {{/unless}} + {{/if}} + + + {isLoading + ? "Checking connection..." + : isConnected + ? "Connected to API" + : "API Disconnected"} + + + {isLoading && ( + + )} + {!isLoading && isConnected && ( + + )} + {!isLoading && !isConnected && ( + + )} + + + + {{/unless}} + + {{#if (and (eq backend "convex") (eq auth "clerk"))}} + + + + Hello {user?.emailAddresses[0].emailAddress} + + + Private Data: {privateData?.message} + + + + + + + + Sign in + + + Sign up + + + + + Loading... + + {{/if}} + + ); +} \ No newline at end of file diff --git a/apps/cli/templates/frontend/native/uniwind/app/+not-found.tsx.hbs b/apps/cli/templates/frontend/native/uniwind/app/+not-found.tsx.hbs new file mode 100644 index 000000000..3817db34e --- /dev/null +++ b/apps/cli/templates/frontend/native/uniwind/app/+not-found.tsx.hbs @@ -0,0 +1,32 @@ +import { Container } from "@/components/container"; +import { Link, Stack } from "expo-router"; +import { Text, View, Pressable } from "react-native"; +import { Card } from "heroui-native"; + +export default function NotFoundScreen() { + return ( + <> + + + + + 🤔 + + Page Not Found + + + Sorry, the page you're looking for doesn't exist. + + + + + Go to Home + + + + + + + + ); +} diff --git a/apps/cli/templates/frontend/native/uniwind/app/_layout.tsx.hbs b/apps/cli/templates/frontend/native/uniwind/app/_layout.tsx.hbs new file mode 100644 index 000000000..79dd278a0 --- /dev/null +++ b/apps/cli/templates/frontend/native/uniwind/app/_layout.tsx.hbs @@ -0,0 +1,131 @@ +{{#if (includes examples "ai")}} +import "@/polyfills"; +{{/if}} + +import "@/global.css"; + +{{#if (eq backend "convex")}} + {{#if (eq auth "better-auth")}} + import { ConvexReactClient } from "convex/react"; + import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react"; + import { authClient } from "@/lib/auth-client"; + {{else}} + import { ConvexProvider, ConvexReactClient } from "convex/react"; + {{/if}} + + {{#if (eq auth "clerk")}} + import { ClerkProvider, useAuth } from "@clerk/clerk-expo"; + import { ConvexProviderWithClerk } from "convex/react-clerk"; + import { tokenCache } from "@clerk/clerk-expo/token-cache"; + {{/if}} +{{else}} + {{#unless (eq api "none")}} + import { QueryClientProvider } from "@tanstack/react-query"; + {{/unless}} +{{/if}} + +import { Stack } from "expo-router"; +import { HeroUINativeProvider } from "heroui-native"; +import { GestureHandlerRootView } from "react-native-gesture-handler"; +import { KeyboardProvider } from "react-native-keyboard-controller"; +import { AppThemeProvider } from "@/contexts/app-theme-context"; + +{{#if (eq api "trpc")}} + import { queryClient } from "@/utils/trpc"; +{{/if}} +{{#if (eq api "orpc")}} + import { queryClient } from "@/utils/orpc"; +{{/if}} + +export const unstable_settings = { + initialRouteName: "(drawer)", +}; + +{{#if (eq backend "convex")}} + const convexUrl = process.env.EXPO_PUBLIC_CONVEX_URL || ""; + const convex = new ConvexReactClient(convexUrl, { + unsavedChangesWarning: false, + }); +{{/if}} + +function StackLayout() { + return ( + + + {{#if (eq auth "clerk")}} + + {{/if}} + + + ); +} + +export default function Layout() { + return ( + {{#if (eq backend "convex")}} + {{#if (eq auth "clerk")}} + + + + + + + + + + + + + + {{else if (eq auth "better-auth")}} + + + + + + + + + + + + {{else}} + + + + + + + + + + + + {{/if}} + {{else}} + {{#unless (eq api "none")}} + + + + + + + + + + + + {{else}} + + + + + + + + + + {{/unless}} + {{/if}} + ); +} \ No newline at end of file diff --git a/apps/cli/templates/frontend/native/uniwind/app/modal.tsx.hbs b/apps/cli/templates/frontend/native/uniwind/app/modal.tsx.hbs new file mode 100644 index 000000000..d2d0c31f1 --- /dev/null +++ b/apps/cli/templates/frontend/native/uniwind/app/modal.tsx.hbs @@ -0,0 +1,53 @@ +import { Container } from "@/components/container"; +import { Text, View, Pressable } from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +import { router } from "expo-router"; +import { Card, useThemeColor } from "heroui-native"; + +function Modal() { + const accentForegroundColor = useThemeColor("accent-foreground"); + + function handleClose() { + router.back(); + } + + return ( + + + + + + + + + Modal Screen + + + This is an example modal screen. You can use this pattern for + dialogs, confirmations, or any overlay content. + + + + + + + Close Modal + + + + + + + + + ); +} + +export default Modal; diff --git a/apps/cli/templates/frontend/native/uniwind/components/container.tsx.hbs b/apps/cli/templates/frontend/native/uniwind/components/container.tsx.hbs new file mode 100644 index 000000000..b324c66d9 --- /dev/null +++ b/apps/cli/templates/frontend/native/uniwind/components/container.tsx.hbs @@ -0,0 +1,33 @@ +import { cn } from "heroui-native"; +import { type PropsWithChildren } from "react"; +import { ScrollView, View, type ViewProps } from "react-native"; +import Animated, { type AnimatedProps } from "react-native-reanimated"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +const AnimatedView = Animated.createAnimatedComponent(View); + +type Props = AnimatedProps & { + className?: string; +}; + +export function Container({ + children, + className, + ...props +}: PropsWithChildren) { + const insets = useSafeAreaInsets(); + + return ( + + + {children} + + + ); +} diff --git a/apps/cli/templates/frontend/native/uniwind/components/theme-toggle.tsx.hbs b/apps/cli/templates/frontend/native/uniwind/components/theme-toggle.tsx.hbs new file mode 100644 index 000000000..2ab0b21a6 --- /dev/null +++ b/apps/cli/templates/frontend/native/uniwind/components/theme-toggle.tsx.hbs @@ -0,0 +1,35 @@ +import { Ionicons } from '@expo/vector-icons'; +import * as Haptics from 'expo-haptics'; +import { Platform, Pressable } from 'react-native'; +import Animated, { FadeOut, ZoomIn } from 'react-native-reanimated'; +import { withUniwind } from 'uniwind'; +import { useAppTheme } from '@/contexts/app-theme-context'; + +const StyledIonicons = withUniwind(Ionicons); + +export function ThemeToggle() { + const { toggleTheme, isLight } = useAppTheme(); + + return ( + { + if (Platform.OS === 'ios') { + Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light); + } + toggleTheme(); + }} + className="px-2.5" + > + {isLight ? ( + + + + ) : ( + + + + )} + + ); +} + diff --git a/apps/cli/templates/frontend/native/uniwind/contexts/app-theme-context.tsx.hbs b/apps/cli/templates/frontend/native/uniwind/contexts/app-theme-context.tsx.hbs new file mode 100644 index 000000000..1cd958b06 --- /dev/null +++ b/apps/cli/templates/frontend/native/uniwind/contexts/app-theme-context.tsx.hbs @@ -0,0 +1,62 @@ +import React, { createContext, useCallback, useContext, useMemo } from 'react'; +import { Uniwind, useUniwind } from 'uniwind'; + +type ThemeName = 'light' | 'dark'; + +type AppThemeContextType = { + currentTheme: string; + isLight: boolean; + isDark: boolean; + setTheme: (theme: ThemeName) => void; + toggleTheme: () => void; +} + +const AppThemeContext = createContext( + undefined +); + +export const AppThemeProvider = ({ children }: { children: React.ReactNode }) => { + const { theme } = useUniwind(); + + const isLight = useMemo(() => { + return theme === 'light'; + }, [theme]); + + const isDark = useMemo(() => { + return theme === 'dark'; + }, [theme]); + + const setTheme = useCallback((newTheme: ThemeName) => { + Uniwind.setTheme(newTheme); + }, []); + + const toggleTheme = useCallback(() => { + Uniwind.setTheme(theme === 'light' ? 'dark' : 'light'); + }, [theme]); + + const value = useMemo( + () => ({ + currentTheme: theme, + isLight, + isDark, + setTheme, + toggleTheme, + }), + [theme, isLight, isDark, setTheme, toggleTheme] + ); + + return ( + + {children} + + ); +}; + +export function useAppTheme() { + const context = useContext(AppThemeContext); + if (!context) { + throw new Error('useAppTheme must be used within AppThemeProvider'); + } + return context; +} + diff --git a/apps/cli/templates/frontend/native/uniwind/global.css b/apps/cli/templates/frontend/native/uniwind/global.css new file mode 100644 index 000000000..3fb7bc04b --- /dev/null +++ b/apps/cli/templates/frontend/native/uniwind/global.css @@ -0,0 +1,5 @@ +@import 'tailwindcss'; +@import 'uniwind'; +@import 'heroui-native/styles'; + +@source './node_modules/heroui-native/lib'; \ No newline at end of file diff --git a/apps/cli/templates/frontend/native/uniwind/metro.config.js.hbs b/apps/cli/templates/frontend/native/uniwind/metro.config.js.hbs new file mode 100644 index 000000000..016ed2562 --- /dev/null +++ b/apps/cli/templates/frontend/native/uniwind/metro.config.js.hbs @@ -0,0 +1,13 @@ +const { getDefaultConfig } = require("expo/metro-config"); +const { withUniwindConfig } = require("uniwind/metro"); + +/** @type {import('expo/metro-config').MetroConfig} */ +const config = getDefaultConfig(__dirname); + +const uniwindConfig = withUniwindConfig(config, { + cssEntryFile: "./global.css", + dtsFile: "./uniwind-types.d.ts", +}); + +module.exports = uniwindConfig; + diff --git a/apps/cli/templates/frontend/native/uniwind/package.json.hbs b/apps/cli/templates/frontend/native/uniwind/package.json.hbs new file mode 100644 index 000000000..b4127d0fa --- /dev/null +++ b/apps/cli/templates/frontend/native/uniwind/package.json.hbs @@ -0,0 +1,54 @@ +{ + "name": "native", + "version": "1.0.0", + "main": "expo-router/entry", + "scripts": { + "start": "expo start", + "dev": "expo start --clear", + "android": "expo run:android", + "ios": "expo run:ios", + "prebuild": "expo prebuild", + "web": "expo start --web" + }, + "dependencies": { + "@expo/metro-runtime": "~6.1.2", + "@expo/vector-icons": "^15.0.3", + "@gorhom/bottom-sheet": "^5", + "@react-navigation/drawer": "^7.3.9", + "@react-navigation/elements": "^2.8.1", + {{#if (includes examples "ai")}} + "@stardazed/streams-text-encoding": "^1.0.2", + "@ungap/structured-clone": "^1.3.0", + {{/if}} + "expo": "^54.0.23", + "expo-constants": "~18.0.10", + "expo-font": "~14.0.9", + "expo-haptics": "^15.0.7", + "expo-linking": "~8.0.8", + "expo-router": "~6.0.14", + "expo-secure-store": "~15.0.7", + "expo-status-bar": "~3.0.8", + "heroui-native": "^1.0.0-beta.1", + "react": "19.1.0", + "react-dom": "19.1.0", + "react-native": "0.81.5", + "react-native-gesture-handler": "~2.28.0", + "react-native-keyboard-controller": "1.18.5", + "react-native-reanimated": "~4.1.0", + "react-native-safe-area-context": "5.6.0", + "react-native-screens": "~4.16.0", + "react-native-svg": "15.12.1", + "react-native-web": "^0.21.0", + "react-native-worklets": "0.5.1", + "tailwind-merge": "^3.3.1", + "tailwind-variants": "^3.1.0", + "tailwindcss": "~4.1.16", + "uniwind": "^1.0.0" + }, + "devDependencies": { + "@types/node": "^24.10.0", + "@types/react": "~19.1.0", + "typescript": "~5.9.2" + }, + "private": true +} \ No newline at end of file diff --git a/apps/cli/templates/frontend/native/nativewind/tsconfig.json.hbs b/apps/cli/templates/frontend/native/uniwind/tsconfig.json.hbs similarity index 52% rename from apps/cli/templates/frontend/native/nativewind/tsconfig.json.hbs rename to apps/cli/templates/frontend/native/uniwind/tsconfig.json.hbs index bf74d1d7b..faf0b6d64 100644 --- a/apps/cli/templates/frontend/native/nativewind/tsconfig.json.hbs +++ b/apps/cli/templates/frontend/native/uniwind/tsconfig.json.hbs @@ -2,17 +2,13 @@ "extends": "expo/tsconfig.base", "compilerOptions": { "strict": true, + "baseUrl": ".", "paths": { - "@/*": [ - "./*" - ] + "@/*": ["./*"] } }, "include": [ "**/*.ts", - "**/*.tsx", - ".expo/types/**/*.ts", - "expo-env.d.ts", - "nativewind-env.d.ts" + "**/*.tsx" ] -} +} \ No newline at end of file diff --git a/apps/cli/test/addons.test.ts b/apps/cli/test/addons.test.ts index faa61dee7..b9e087518 100644 --- a/apps/cli/test/addons.test.ts +++ b/apps/cli/test/addons.test.ts @@ -77,7 +77,8 @@ describe("Addon Configurations", () => { const pwaIncompatibleFrontends = [ "nuxt", "svelte", - "native-nativewind", + "native-bare", + "native-uniwind", "native-unistyles", ]; @@ -154,7 +155,8 @@ describe("Addon Configurations", () => { } const tauriIncompatibleFrontends = [ - "native-nativewind", + "native-bare", + "native-uniwind", "native-unistyles", ]; diff --git a/apps/cli/test/api.test.ts b/apps/cli/test/api.test.ts index 360da679f..f6973296e 100644 --- a/apps/cli/test/api.test.ts +++ b/apps/cli/test/api.test.ts @@ -49,7 +49,7 @@ describe("API Configurations", () => { }); it("should work with tRPC + native frontends", async () => { - const nativeFrontends = ["native-nativewind", "native-unistyles"]; + const nativeFrontends = ["native-bare", "native-uniwind", "native-unistyles"]; for (const frontend of nativeFrontends) { const result = await runTRPCTest({ @@ -181,7 +181,8 @@ describe("API Configurations", () => { "nuxt", "svelte", "solid", - "native-nativewind", + "native-bare", + "native-uniwind", "native-unistyles", ]; @@ -565,7 +566,7 @@ describe("API Configurations", () => { const result = await runTRPCTest({ projectName: "api-complex-frontend", api: "trpc", - frontend: ["tanstack-router", "native-nativewind"], // Web + Native + frontend: ["tanstack-router", "native-bare"], // Web + Native backend: "hono", runtime: "bun", database: "sqlite", diff --git a/apps/cli/test/auth.test.ts b/apps/cli/test/auth.test.ts index 95d5f2d11..656966ea1 100644 --- a/apps/cli/test/auth.test.ts +++ b/apps/cli/test/auth.test.ts @@ -127,7 +127,8 @@ describe("Authentication Configurations", () => { "nuxt", "svelte", "solid", - "native-nativewind", + "native-bare", + "native-uniwind", "native-unistyles", ]; @@ -213,7 +214,8 @@ describe("Authentication Configurations", () => { "react-router", "tanstack-start", "next", - "native-nativewind", + "native-bare", + "native-uniwind", "native-unistyles", ]; @@ -455,7 +457,7 @@ describe("Authentication Configurations", () => { database: "sqlite", orm: "drizzle", api: "trpc", - frontend: ["tanstack-router", "native-nativewind"], + frontend: ["tanstack-router", "native-bare"], addons: ["turborepo"], examples: ["todo"], dbSetup: "none", diff --git a/apps/cli/test/deployment.test.ts b/apps/cli/test/deployment.test.ts index 8e435f4d5..b1489ef51 100644 --- a/apps/cli/test/deployment.test.ts +++ b/apps/cli/test/deployment.test.ts @@ -63,7 +63,7 @@ describe("Deployment Configurations", () => { projectName: "web-deploy-no-web-frontend-fail", webDeploy: "wrangler", serverDeploy: "none", - frontend: ["native-nativewind"], // Native frontend only + frontend: ["native-bare"], // Native frontend only backend: "hono", runtime: "bun", database: "sqlite", @@ -84,7 +84,7 @@ describe("Deployment Configurations", () => { projectName: "web-deploy-mixed-frontends", webDeploy: "wrangler", serverDeploy: "none", - frontend: ["tanstack-router", "native-nativewind"], + frontend: ["tanstack-router", "native-bare"], backend: "hono", runtime: "bun", database: "sqlite", @@ -541,7 +541,7 @@ describe("Deployment Configurations", () => { orm: "none", auth: "none", api: "none", - frontend: ["native-nativewind"], // Only native frontend + frontend: ["native-bare"], // Only native frontend addons: ["none"], examples: ["none"], dbSetup: "none", diff --git a/apps/cli/test/frontend.test.ts b/apps/cli/test/frontend.test.ts index 7879ba006..671b13981 100644 --- a/apps/cli/test/frontend.test.ts +++ b/apps/cli/test/frontend.test.ts @@ -14,7 +14,8 @@ describe("Frontend Configurations", () => { "tanstack-start", "next", "nuxt", - "native-nativewind", + "native-bare", + "native-uniwind", "native-unistyles", "svelte", "solid", @@ -24,7 +25,8 @@ describe("Frontend Configurations", () => { | "tanstack-start" | "next" | "nuxt" - | "native-nativewind" + | "native-bare" + | "native-uniwind" | "native-unistyles" | "svelte" | "solid" @@ -337,7 +339,7 @@ describe("Frontend Configurations", () => { it("should fail with multiple native frontends", async () => { const result = await runTRPCTest({ projectName: "multiple-native-fail", - frontend: ["native-nativewind", "native-unistyles"], + frontend: ["native-bare", "native-unistyles"], backend: "hono", runtime: "bun", database: "sqlite", @@ -358,7 +360,7 @@ describe("Frontend Configurations", () => { it("should work with one web + one native frontend", async () => { const result = await runTRPCTest({ projectName: "web-native-combo", - frontend: ["tanstack-router", "native-nativewind"], + frontend: ["tanstack-router", "native-bare"], backend: "hono", runtime: "bun", database: "sqlite", @@ -490,7 +492,7 @@ describe("Frontend Configurations", () => { it("should fail with web deploy but no web frontend", async () => { const result = await runTRPCTest({ projectName: "web-deploy-no-frontend-fail", - frontend: ["native-nativewind"], + frontend: ["native-bare"], webDeploy: "wrangler", backend: "hono", runtime: "bun", diff --git a/apps/cli/test/integration.test.ts b/apps/cli/test/integration.test.ts index 268c9be35..028655126 100644 --- a/apps/cli/test/integration.test.ts +++ b/apps/cli/test/integration.test.ts @@ -123,7 +123,7 @@ describe("Integration Tests - Real World Scenarios", () => { orm: "drizzle", auth: "better-auth", api: "trpc", - frontend: ["native-nativewind"], + frontend: ["native-bare"], addons: ["biome", "turborepo"], examples: ["todo"], dbSetup: "none", @@ -383,7 +383,7 @@ describe("Integration Tests - Real World Scenarios", () => { orm: "drizzle", auth: "none", api: "trpc", - frontend: ["native-nativewind"], + frontend: ["native-bare"], addons: ["pwa"], // PWA not compatible with native-only examples: ["none"], dbSetup: "none", @@ -450,7 +450,7 @@ describe("Integration Tests - Real World Scenarios", () => { orm: "drizzle", auth: "none", api: "trpc", - frontend: ["native-nativewind"], // Only native, no web + frontend: ["native-bare"], // Only native, no web addons: ["none"], examples: ["none"], dbSetup: "none", @@ -473,7 +473,7 @@ describe("Integration Tests - Real World Scenarios", () => { orm: "drizzle", auth: "better-auth", api: "trpc", - frontend: ["tanstack-router", "native-nativewind"], + frontend: ["tanstack-router", "native-bare"], addons: ["biome", "husky", "turborepo"], examples: ["todo", "ai"], dbSetup: "none", From ddad5ea5eba651640e6a2e75eb2471e7e6bce821 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Sun, 9 Nov 2025 22:40:07 +0530 Subject: [PATCH 2/5] fix --- apps/cli/src/helpers/core/template-manager.ts | 10 +- .../native/bare/components/sign-in.tsx.hbs | 135 ++++++ .../native/bare/components/sign-up.tsx.hbs | 146 ++++++ .../native/bare/components/sign-in.tsx.hbs | 127 +++++ .../native/bare/components/sign-up.tsx.hbs | 153 ++++++ .../ai/native/bare/app/(drawer)/ai.tsx.hbs | 293 ++++++++++++ .../examples/ai/native/bare/polyfills.js | 26 ++ .../native/bare/app/(drawer)/todos.tsx.hbs | 440 ++++++++++++++++++ .../bare/app/(drawer)/(tabs)/_layout.tsx.hbs | 12 +- .../native/bare/app/(drawer)/_layout.tsx.hbs | 20 +- .../native/bare/app/(drawer)/index.tsx.hbs | 54 +-- .../frontend/native/bare/app/_layout.tsx.hbs | 234 +++++----- .../native/bare/components/container.tsx.hbs | 3 +- .../bare/components/header-button.tsx.hbs | 2 +- .../frontend/native/uniwind/app.json.hbs | 31 +- 15 files changed, 1507 insertions(+), 179 deletions(-) create mode 100644 apps/cli/templates/auth/better-auth/convex/native/bare/components/sign-in.tsx.hbs create mode 100644 apps/cli/templates/auth/better-auth/convex/native/bare/components/sign-up.tsx.hbs create mode 100644 apps/cli/templates/auth/better-auth/native/bare/components/sign-in.tsx.hbs create mode 100644 apps/cli/templates/auth/better-auth/native/bare/components/sign-up.tsx.hbs create mode 100644 apps/cli/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs create mode 100644 apps/cli/templates/examples/ai/native/bare/polyfills.js create mode 100644 apps/cli/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs diff --git a/apps/cli/src/helpers/core/template-manager.ts b/apps/cli/src/helpers/core/template-manager.ts index 55f80dc71..ec3b3f92d 100644 --- a/apps/cli/src/helpers/core/template-manager.ts +++ b/apps/cli/src/helpers/core/template-manager.ts @@ -444,9 +444,6 @@ export async function setupAuthTemplate( ); } - const hasNativeBare = context.frontend.includes("native-bare"); - const hasUniwind = context.frontend.includes("native-uniwind"); - const hasUnistyles = context.frontend.includes("native-unistyles"); let nativeFrameworkPath = ""; if (hasNativeBare) nativeFrameworkPath = "bare"; else if (hasUniwind) nativeFrameworkPath = "uniwind"; @@ -535,7 +532,8 @@ export async function setupAuthTemplate( } let nativeFrameworkPath = ""; - if (hasUniwind) nativeFrameworkPath = "uniwind"; + if (hasNativeBare) nativeFrameworkPath = "bare"; + else if (hasUniwind) nativeFrameworkPath = "uniwind"; else if (hasUnistyles) nativeFrameworkPath = "unistyles"; if (nativeFrameworkPath) { const convexBetterAuthNativeFrameworkSrc = path.join( @@ -696,7 +694,9 @@ export async function setupAuthTemplate( } let nativeFrameworkAuthPath = ""; - if (hasUniwind) { + if (hasNativeBare) { + nativeFrameworkAuthPath = "bare"; + } else if (hasUniwind) { nativeFrameworkAuthPath = "uniwind"; } else if (hasUnistyles) { nativeFrameworkAuthPath = "unistyles"; diff --git a/apps/cli/templates/auth/better-auth/convex/native/bare/components/sign-in.tsx.hbs b/apps/cli/templates/auth/better-auth/convex/native/bare/components/sign-in.tsx.hbs new file mode 100644 index 000000000..68b206fe4 --- /dev/null +++ b/apps/cli/templates/auth/better-auth/convex/native/bare/components/sign-in.tsx.hbs @@ -0,0 +1,135 @@ +import { authClient } from "@/lib/auth-client"; +import { useState } from "react"; +import { + ActivityIndicator, + Text, + TextInput, + TouchableOpacity, + View, + StyleSheet, +} from "react-native"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { NAV_THEME } from "@/lib/constants"; + +function SignIn() { + const { colorScheme } = useColorScheme(); + const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + async function handleLogin() { + setIsLoading(true); + setError(null); + + await authClient.signIn.email( + { + email, + password, + }, + { + onError(error) { + setError(error.error?.message || "Failed to sign in"); + setIsLoading(false); + }, + onSuccess() { + setEmail(""); + setPassword(""); + }, + onFinished() { + setIsLoading(false); + }, + } + ); + } + + return ( + + Sign In + + {error ? ( + + {error} + + ) : null} + + + + + + + {isLoading ? ( + + ) : ( + Sign In + )} + + + ); +} + +const styles = StyleSheet.create({ + card: { + marginTop: 24, + padding: 16, + borderRadius: 12, + borderWidth: 1, + }, + title: { + fontSize: 18, + fontWeight: "600", + marginBottom: 16, + }, + errorContainer: { + marginBottom: 16, + padding: 12, + borderRadius: 8, + }, + errorText: { + fontSize: 14, + }, + input: { + borderWidth: 1, + borderRadius: 8, + paddingVertical: 12, + paddingHorizontal: 16, + fontSize: 16, + marginBottom: 12, + }, + button: { + paddingVertical: 12, + paddingHorizontal: 16, + borderRadius: 8, + alignItems: "center", + justifyContent: "center", + marginTop: 4, + }, + buttonText: { + color: "#ffffff", + fontSize: 16, + fontWeight: "500", + }, +}); + +export { SignIn }; + diff --git a/apps/cli/templates/auth/better-auth/convex/native/bare/components/sign-up.tsx.hbs b/apps/cli/templates/auth/better-auth/convex/native/bare/components/sign-up.tsx.hbs new file mode 100644 index 000000000..526cf9ec0 --- /dev/null +++ b/apps/cli/templates/auth/better-auth/convex/native/bare/components/sign-up.tsx.hbs @@ -0,0 +1,146 @@ +import { authClient } from "@/lib/auth-client"; +import { useState } from "react"; +import { + ActivityIndicator, + Text, + TextInput, + TouchableOpacity, + View, + StyleSheet, +} from "react-native"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { NAV_THEME } from "@/lib/constants"; + +function SignUp() { + const { colorScheme } = useColorScheme(); + const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; + const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + async function handleSignUp() { + setIsLoading(true); + setError(null); + + await authClient.signUp.email( + { + name, + email, + password, + }, + { + onError(error) { + setError(error.error?.message || "Failed to sign up"); + setIsLoading(false); + }, + onSuccess() { + setName(""); + setEmail(""); + setPassword(""); + }, + onFinished() { + setIsLoading(false); + }, + } + ); + } + + return ( + + Create Account + + {error ? ( + + {error} + + ) : null} + + + + + + + + + {isLoading ? ( + + ) : ( + Sign Up + )} + + + ); +} + +const styles = StyleSheet.create({ + card: { + marginTop: 24, + padding: 16, + borderRadius: 12, + borderWidth: 1, + }, + title: { + fontSize: 18, + fontWeight: "600", + marginBottom: 16, + }, + errorContainer: { + marginBottom: 16, + padding: 12, + borderRadius: 8, + }, + errorText: { + fontSize: 14, + }, + input: { + borderWidth: 1, + borderRadius: 8, + paddingVertical: 12, + paddingHorizontal: 16, + fontSize: 16, + marginBottom: 12, + }, + button: { + paddingVertical: 12, + paddingHorizontal: 16, + borderRadius: 8, + alignItems: "center", + justifyContent: "center", + marginTop: 4, + }, + buttonText: { + color: "#ffffff", + fontSize: 16, + fontWeight: "500", + }, +}); + +export { SignUp }; + diff --git a/apps/cli/templates/auth/better-auth/native/bare/components/sign-in.tsx.hbs b/apps/cli/templates/auth/better-auth/native/bare/components/sign-in.tsx.hbs new file mode 100644 index 000000000..36ee65f21 --- /dev/null +++ b/apps/cli/templates/auth/better-auth/native/bare/components/sign-in.tsx.hbs @@ -0,0 +1,127 @@ +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, +Text, +TextInput, +TouchableOpacity, +View, +StyleSheet, +} from "react-native"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { NAV_THEME } from "@/lib/constants"; + +function SignIn() { +const { colorScheme } = useColorScheme(); +const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; +const [email, setEmail] = useState(""); +const [password, setPassword] = useState(""); +const [isLoading, setIsLoading] = useState(false); +const [error, setError] = useState(null); + + async function handleLogin() { + setIsLoading(true); + setError(null); + + await authClient.signIn.email( + { + email, + password, + }, + { + onError(error) { + setError(error.error?.message || "Failed to sign in"); + setIsLoading(false); + }, + onSuccess() { + setEmail(""); + setPassword(""); + queryClient.refetchQueries(); + }, + onFinished() { + setIsLoading(false); + }, + } + ); + } + + return ( + + Sign In + + {error ? ( + + {error} + + ) : null} + + + + + + + {isLoading ? ( + + ) : ( + Sign In + )} + + + ); + } + + const styles = StyleSheet.create({ + card: { + marginTop: 24, + padding: 16, + borderRadius: 12, + borderWidth: 1, + }, + title: { + fontSize: 18, + fontWeight: "600", + marginBottom: 16, + }, + errorContainer: { + marginBottom: 16, + padding: 12, + borderRadius: 8, + }, + errorText: { + fontSize: 14, + }, + input: { + borderWidth: 1, + borderRadius: 8, + paddingVertical: 12, + paddingHorizontal: 16, + fontSize: 16, + marginBottom: 12, + }, + button: { + paddingVertical: 12, + paddingHorizontal: 16, + borderRadius: 8, + alignItems: "center", + justifyContent: "center", + marginTop: 4, + }, + buttonText: { + color: "#ffffff", + fontSize: 16, + fontWeight: "500", + }, + }); + + export { SignIn }; \ No newline at end of file diff --git a/apps/cli/templates/auth/better-auth/native/bare/components/sign-up.tsx.hbs b/apps/cli/templates/auth/better-auth/native/bare/components/sign-up.tsx.hbs new file mode 100644 index 000000000..e48e318ba --- /dev/null +++ b/apps/cli/templates/auth/better-auth/native/bare/components/sign-up.tsx.hbs @@ -0,0 +1,153 @@ +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, + Text, + TextInput, + TouchableOpacity, + View, + StyleSheet, +} from "react-native"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { NAV_THEME } from "@/lib/constants"; + +function SignUp() { + const { colorScheme } = useColorScheme(); + const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; + const [name, setName] = useState(""); + const [email, setEmail] = useState(""); + const [password, setPassword] = useState(""); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + + async function handleSignUp() { + setIsLoading(true); + setError(null); + + await authClient.signUp.email( + { + name, + email, + password, + }, + { + onError(error) { + setError(error.error?.message || "Failed to sign up"); + setIsLoading(false); + }, + onSuccess() { + setName(""); + setEmail(""); + setPassword(""); + queryClient.refetchQueries(); + }, + onFinished() { + setIsLoading(false); + }, + } + ); + } + + return ( + + Create Account + + {error ? ( + + {error} + + ) : null} + + + + + + + + + {isLoading ? ( + + ) : ( + Sign Up + )} + + + ); +} + +const styles = StyleSheet.create({ + card: { + marginTop: 24, + padding: 16, + borderRadius: 12, + borderWidth: 1, + }, + title: { + fontSize: 18, + fontWeight: "600", + marginBottom: 16, + }, + errorContainer: { + marginBottom: 16, + padding: 12, + borderRadius: 8, + }, + errorText: { + fontSize: 14, + }, + input: { + borderWidth: 1, + borderRadius: 8, + paddingVertical: 12, + paddingHorizontal: 16, + fontSize: 16, + marginBottom: 12, + }, + button: { + paddingVertical: 12, + paddingHorizontal: 16, + borderRadius: 8, + alignItems: "center", + justifyContent: "center", + marginTop: 4, + }, + buttonText: { + color: "#ffffff", + fontSize: 16, + fontWeight: "500", + }, +}); + +export { SignUp }; + diff --git a/apps/cli/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs b/apps/cli/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs new file mode 100644 index 000000000..6e436d362 --- /dev/null +++ b/apps/cli/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs @@ -0,0 +1,293 @@ +import { useRef, useEffect, useState } from "react"; +import { + View, + Text, + TextInput, + TouchableOpacity, + ScrollView, + KeyboardAvoidingView, + Platform, + StyleSheet, +} from "react-native"; +import { useChat } from "@ai-sdk/react"; +import { DefaultChatTransport } from "ai"; +import { fetch as expoFetch } from "expo/fetch"; +import { Ionicons } from "@expo/vector-icons"; +import { Container } from "@/components/container"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { NAV_THEME } from "@/lib/constants"; + +const generateAPIUrl = (relativePath: string) => { + const serverUrl = process.env.EXPO_PUBLIC_SERVER_URL; + if (!serverUrl) { + throw new Error( + "EXPO_PUBLIC_SERVER_URL environment variable is not defined" + ); + } + const path = relativePath.startsWith("/") ? relativePath : `/${relativePath}`; + return serverUrl.concat(path); +}; + +export default function AIScreen() { + const { colorScheme } = useColorScheme(); + const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; + const [input, setInput] = useState(""); + const { messages, error, sendMessage } = useChat({ + transport: new DefaultChatTransport({ + fetch: expoFetch as unknown as typeof globalThis.fetch, + api: generateAPIUrl("/ai"), + }), + onError: (error) => console.error(error, "AI Chat Error"), + }); + const scrollViewRef = useRef(null); + + useEffect(() => { + scrollViewRef.current?.scrollToEnd({ animated: true }); + }, [messages]); + + function onSubmit() { + const value = input.trim(); + if (value) { + sendMessage({ text: value }); + setInput(""); + } + } + + if (error) { + return ( + + + + + Error: {error.message} + + + Please check your connection and try again. + + + + + ); + } + + return ( + + + + + + AI Chat + + + Chat with our AI assistant + + + + {messages.length === 0 ? ( + + + Ask me anything to get started! + + + ) : ( + + {messages.map((message) => ( + + + {message.role === "user" ? "You" : "AI Assistant"} + + + {message.parts.map((part, i) => + part.type === "text" ? ( + + {part.text} + + ) : ( + + {JSON.stringify(part)} + + ) + )} + + + ))} + + )} + + + + { + e.preventDefault(); + onSubmit(); + }} + autoFocus={true} + multiline + /> + + + + + + + + + ); +} + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, + content: { + flex: 1, + paddingHorizontal: 16, + paddingTop: 24, + }, + header: { + marginBottom: 24, + }, + headerTitle: { + fontSize: 28, + fontWeight: "bold", + marginBottom: 8, + }, + headerSubtitle: { + fontSize: 16, + }, + scrollView: { + flex: 1, + marginBottom: 16, + }, + emptyContainer: { + flex: 1, + justifyContent: "center", + alignItems: "center", + }, + emptyText: { + fontSize: 18, + textAlign: "center", + }, + messagesList: { + gap: 12, + paddingBottom: 16, + }, + messageCard: { + borderWidth: 1, + borderRadius: 12, + padding: 12, + maxWidth: "80%", + }, + messageRole: { + fontSize: 12, + fontWeight: "600", + marginBottom: 4, + }, + messageParts: { + gap: 4, + }, + messageText: { + fontSize: 14, + lineHeight: 20, + }, + inputContainer: { + borderTopWidth: 1, + paddingTop: 16, + }, + inputRow: { + flexDirection: "row", + alignItems: "flex-end", + gap: 8, + }, + input: { + flex: 1, + borderWidth: 1, + borderRadius: 8, + paddingHorizontal: 12, + paddingVertical: 8, + fontSize: 16, + minHeight: 40, + maxHeight: 120, + }, + sendButton: { + padding: 10, + borderRadius: 8, + justifyContent: "center", + alignItems: "center", + }, + errorContainer: { + flex: 1, + justifyContent: "center", + alignItems: "center", + paddingHorizontal: 16, + }, + errorCard: { + borderWidth: 1, + borderRadius: 12, + padding: 16, + }, + errorTitle: { + fontSize: 18, + fontWeight: "600", + marginBottom: 8, + textAlign: "center", + }, + errorText: { + fontSize: 14, + textAlign: "center", + }, +}); + diff --git a/apps/cli/templates/examples/ai/native/bare/polyfills.js b/apps/cli/templates/examples/ai/native/bare/polyfills.js new file mode 100644 index 000000000..8bbb2b269 --- /dev/null +++ b/apps/cli/templates/examples/ai/native/bare/polyfills.js @@ -0,0 +1,26 @@ +import structuredClone from "@ungap/structured-clone"; +import { Platform } from "react-native"; + +if (Platform.OS !== "web") { + const setupPolyfills = async () => { + const { polyfillGlobal } = await import( + "react-native/Libraries/Utilities/PolyfillFunctions" + ); + + const { TextEncoderStream, TextDecoderStream } = await import( + "@stardazed/streams-text-encoding" + ); + + if (!("structuredClone" in global)) { + polyfillGlobal("structuredClone", () => structuredClone); + } + + polyfillGlobal("TextEncoderStream", () => TextEncoderStream); + polyfillGlobal("TextDecoderStream", () => TextDecoderStream); + }; + + setupPolyfills(); +} + +export {}; + diff --git a/apps/cli/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs b/apps/cli/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs new file mode 100644 index 000000000..bdae56d52 --- /dev/null +++ b/apps/cli/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs @@ -0,0 +1,440 @@ +import { useState } from "react"; +import { + View, + Text, + TextInput, + ScrollView, + ActivityIndicator, + Alert, + TouchableOpacity, + StyleSheet, +} from "react-native"; +import { Ionicons } from "@expo/vector-icons"; +{{#if (eq backend "convex")}} +import { useMutation, useQuery } from "convex/react"; +import { api } from "@{{projectName}}/backend/convex/_generated/api"; +import type { Id } from "@{{projectName}}/backend/convex/_generated/dataModel"; +{{else}} +import { useMutation, useQuery } from "@tanstack/react-query"; +{{/if}} +import { Container } from "@/components/container"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { NAV_THEME } from "@/lib/constants"; +{{#unless (eq backend "convex")}} + {{#if (eq api "orpc")}} + import { orpc } from "@/utils/orpc"; + {{/if}} + {{#if (eq api "trpc")}} + import { trpc } from "@/utils/trpc"; + {{/if}} +{{/unless}} + +export default function TodosScreen() { + const { colorScheme } = useColorScheme(); + const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; + const [newTodoText, setNewTodoText] = useState(""); + + {{#if (eq backend "convex")}} + const todos = useQuery(api.todos.getAll); + const createTodoMutation = useMutation(api.todos.create); + const toggleTodoMutation = useMutation(api.todos.toggle); + const deleteTodoMutation = useMutation(api.todos.deleteTodo); + {{else}} + {{#if (eq api "orpc")}} + const todos = useQuery(orpc.todo.getAll.queryOptions()); + const createMutation = useMutation(orpc.todo.create.mutationOptions({ + onSuccess: () => { + todos.refetch(); + setNewTodoText(""); + }, + })); + const toggleMutation = useMutation(orpc.todo.toggle.mutationOptions({ + onSuccess: () => { + todos.refetch(); + }, + })); + const deleteMutation = useMutation(orpc.todo.delete.mutationOptions({ + onSuccess: () => { + todos.refetch(); + }, + })); + {{/if}} + {{#if (eq api "trpc")}} + const todos = useQuery(trpc.todo.getAll.queryOptions()); + const createMutation = useMutation(trpc.todo.create.mutationOptions({ + onSuccess: () => { + todos.refetch(); + setNewTodoText(""); + }, + })); + const toggleMutation = useMutation(trpc.todo.toggle.mutationOptions({ + onSuccess: () => { + todos.refetch(); + }, + })); + const deleteMutation = useMutation(trpc.todo.delete.mutationOptions({ + onSuccess: () => { + todos.refetch(); + }, + })); + {{/if}} + {{/if}} + + {{#if (eq backend "convex")}} + async function handleAddTodo() { + const text = newTodoText.trim(); + if (!text) return; + await createTodoMutation({ text }); + setNewTodoText(""); + } + + function handleToggleTodo(id: Id<"todos">, currentCompleted: boolean) { + toggleTodoMutation({ id, completed: !currentCompleted }); + } + + function handleDeleteTodo(id: Id<"todos">) { + Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: () => deleteTodoMutation({ id }), + }, + ]); + } + + const isLoading = !todos; + const completedCount = todos?.filter((t) => t.completed).length || 0; + const totalCount = todos?.length || 0; + {{else}} + function handleAddTodo() { + if (newTodoText.trim()) { + createMutation.mutate({ text: newTodoText }); + } + } + + function handleToggleTodo(id: number, completed: boolean) { + toggleMutation.mutate({ id, completed: !completed }); + } + + function handleDeleteTodo(id: number) { + Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: () => deleteMutation.mutate({ id }), + }, + ]); + } + + const isLoading = todos?.isLoading; + const completedCount = todos?.data?.filter((t) => t.completed).length || 0; + const totalCount = todos?.data?.length || 0; + {{/if}} + + return ( + + + + + + Todo List + + {totalCount > 0 && ( + + + {completedCount}/{totalCount} + + + )} + + + + + + + + + + + {{else}} + disabled={createMutation.isPending || !newTodoText.trim()} + style={[ + styles.addButton, + { + backgroundColor: (createMutation.isPending || !newTodoText.trim()) ? theme.border : theme.primary, + opacity: (createMutation.isPending || !newTodoText.trim()) ? 0.5 : 1, + }, + ]} + > + {createMutation.isPending ? ( + + ) : ( + + )} + {{/if}} + + + + + {{#if (eq backend "convex")}} + {isLoading && ( + + + + Loading todos... + + + )} + + {todos && todos.length === 0 && !isLoading && ( + + + + No todos yet + + + Add your first task to get started! + + + )} + + {todos && todos.length > 0 && ( + + {todos.map((todo) => ( + + + handleToggleTodo(todo._id, todo.completed)} + style={[styles.checkbox, { borderColor: theme.border }]} + > + {todo.completed && ( + + )} + + + + {todo.text} + + + handleDeleteTodo(todo._id)} + style=\{{ styles.deleteButton }} + > + + + + + ))} + + )} + {{else}} + {isLoading && ( + + + + Loading todos... + + + )} + + {todos?.data && todos.data.length === 0 && !isLoading && ( + + + + No todos yet + + + Add your first task to get started! + + + )} + + {todos?.data && todos.data.length > 0 && ( + + {todos.data.map((todo) => ( + + + handleToggleTodo(todo.id, todo.completed)} + style={[styles.checkbox, { borderColor: theme.border }]} + > + {todo.completed && ( + + )} + + + + {todo.text} + + + handleDeleteTodo(todo.id)} + style={styles.deleteButton} + > + + + + + ))} + + )} + {{/if}} + + + ); +} + +const styles = StyleSheet.create({ + scrollView: { + flex: 1, + }, + contentContainer: { + padding: 24, + }, + header: { + marginBottom: 24, + }, + headerRow: { + flexDirection: "row", + alignItems: "center", + justifyContent: "space-between", + marginBottom: 8, + }, + title: { + fontSize: 32, + fontWeight: "bold", + }, + badge: { + paddingHorizontal: 12, + paddingVertical: 4, + borderRadius: 12, + }, + badgeText: { + color: "#ffffff", + fontSize: 12, + fontWeight: "600", + }, + inputCard: { + borderWidth: 1, + borderRadius: 12, + padding: 16, + marginBottom: 24, + }, + inputRow: { + flexDirection: "row", + alignItems: "center", + gap: 12, + }, + inputContainer: { + flex: 1, + }, + input: { + borderWidth: 1, + borderRadius: 8, + paddingVertical: 12, + paddingHorizontal: 16, + fontSize: 16, + }, + addButton: { + padding: 12, + borderRadius: 8, + justifyContent: "center", + alignItems: "center", + }, + centerContainer: { + alignItems: "center", + justifyContent: "center", + paddingVertical: 48, + }, + loadingText: { + marginTop: 16, + fontSize: 14, + }, + emptyCard: { + borderWidth: 1, + borderRadius: 12, + padding: 48, + alignItems: "center", + justifyContent: "center", + }, + emptyTitle: { + fontSize: 18, + fontWeight: "600", + marginBottom: 8, + }, + emptyText: { + fontSize: 14, + textAlign: "center", + }, + todosList: { + gap: 12, + }, + todoCard: { + borderWidth: 1, + borderRadius: 12, + padding: 16, + }, + todoRow: { + flexDirection: "row", + alignItems: "center", + gap: 12, + }, + checkbox: { + width: 24, + height: 24, + borderWidth: 2, + borderRadius: 4, + justifyContent: "center", + alignItems: "center", + }, + todoTextContainer: { + flex: 1, + }, + todoText: { + fontSize: 16, + }, + deleteButton: { + padding: 8, + }, +}); + diff --git a/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/_layout.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/_layout.tsx.hbs index 0c479f681..627082b33 100644 --- a/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/_layout.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/_layout.tsx.hbs @@ -9,7 +9,7 @@ export default function TabLayout() { return ( , - }}} + }} /> ( ), - }}} + }} /> ); diff --git a/apps/cli/templates/frontend/native/bare/app/(drawer)/_layout.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/(drawer)/_layout.tsx.hbs index 98374fb4c..3a77a4f5c 100644 --- a/apps/cli/templates/frontend/native/bare/app/(drawer)/_layout.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/app/(drawer)/_layout.tsx.hbs @@ -12,7 +12,7 @@ const DrawerLayout = () => { return ( { color: theme.text, }, drawerInactiveTintColor: theme.text, - }}} + }} > ( ), - }}} + }} /> ( @@ -52,24 +52,24 @@ const DrawerLayout = () => { ), - }}} + }} /> {{#if (includes examples "todo")}} ( ), - }}} + }} /> {{/if}} {{#if (includes examples "ai")}} ( @@ -79,7 +79,7 @@ const DrawerLayout = () => { color={color} /> ), - }}} + }} /> {{/if}} diff --git a/apps/cli/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs index 44b4094a3..645cf3190 100644 --- a/apps/cli/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs @@ -52,20 +52,20 @@ export default function Home() { - + BETTER T STACK {{#unless (and (eq backend "convex") (eq auth "better-auth"))}} - + {{#if (eq backend "convex")}} - + - + Convex - + {healthCheck === undefined ? "Checking..." : healthCheck === "OK" @@ -76,13 +76,13 @@ export default function Home() { {{else}} {{#unless (eq api "none")}} - - - - + + + + {{#if (eq api "orpc")}}ORPC{{else}}TRPC{{/if}} - + {healthCheck.isLoading ? "Checking connection..." : healthCheck.data @@ -98,51 +98,51 @@ export default function Home() { {{#if (and (eq backend "convex") (eq auth "clerk"))}} - Hello {user?.emailAddresses[0].emailAddress} - Private Data: {privateData?.message} + Hello {user?.emailAddresses[0].emailAddress} + Private Data: {privateData?.message} - Sign in + Sign in - Sign up + Sign up - Loading... + Loading... {{/if}} {{#if (and (eq backend "convex") (eq auth "better-auth"))}} {user ? ( - - - - Welcome, {user.name} + + + + Welcome, {user.name} - + {user.email} { authClient.signOut(); }} > - Sign Out + Sign Out ) : null} - - + + API Status - - - + + + {healthCheck === undefined ? "Checking..." : healthCheck === "OK" diff --git a/apps/cli/templates/frontend/native/bare/app/_layout.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/_layout.tsx.hbs index 1f49b24b5..f99595437 100644 --- a/apps/cli/templates/frontend/native/bare/app/_layout.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/app/_layout.tsx.hbs @@ -1,30 +1,32 @@ {{#if (includes examples "ai")}} import "@/polyfills"; {{/if}} + {{#if (eq backend "convex")}} -{{#if (eq auth "better-auth")}} -import { ConvexReactClient } from "convex/react"; -import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react"; -import { authClient } from "@/lib/auth-client"; -{{else}} -import { ConvexProvider, ConvexReactClient } from "convex/react"; -{{/if}} -{{#if (eq auth "clerk")}} -import { ClerkProvider, useAuth } from "@clerk/clerk-expo"; -import { ConvexProviderWithClerk } from "convex/react-clerk"; -import { tokenCache } from "@clerk/clerk-expo/token-cache"; -{{/if}} + {{#if (eq auth "better-auth")}} + import { ConvexReactClient } from "convex/react"; + import { ConvexBetterAuthProvider } from "@convex-dev/better-auth/react"; + import { authClient } from "@/lib/auth-client"; + {{else}} + import { ConvexProvider, ConvexReactClient } from "convex/react"; + {{/if}} + {{#if (eq auth "clerk")}} + import { ClerkProvider, useAuth } from "@clerk/clerk-expo"; + import { ConvexProviderWithClerk } from "convex/react-clerk"; + import { tokenCache } from "@clerk/clerk-expo/token-cache"; + {{/if}} {{else}} -{{#unless (eq api "none")}} -import { QueryClientProvider } from "@tanstack/react-query"; -{{/unless}} + {{#unless (eq api "none")}} + import { QueryClientProvider } from "@tanstack/react-query"; + {{/unless}} {{/if}} + import { Stack } from "expo-router"; import { -DarkTheme, -DefaultTheme, -type Theme, -ThemeProvider, + DarkTheme, + DefaultTheme, + type Theme, + ThemeProvider, } from "@react-navigation/native"; import { StatusBar } from "expo-status-bar"; import { GestureHandlerRootView } from "react-native-gesture-handler"; @@ -41,119 +43,121 @@ import { Platform, StyleSheet } from "react-native"; import { setAndroidNavigationBar } from "@/lib/android-navigation-bar"; const LIGHT_THEME: Theme = { -...DefaultTheme, -colors: NAV_THEME.light, + ...DefaultTheme, + colors: NAV_THEME.light, }; const DARK_THEME: Theme = { -...DarkTheme, -colors: NAV_THEME.dark, + ...DarkTheme, + colors: NAV_THEME.dark, }; export const unstable_settings = { -initialRouteName: "(drawer)", + initialRouteName: "(drawer)", }; {{#if (eq backend "convex")}} const convex = new ConvexReactClient(process.env.EXPO_PUBLIC_CONVEX_URL!, { -unsavedChangesWarning: false, + unsavedChangesWarning: false, }); {{/if}} +const useIsomorphicLayoutEffect = + Platform.OS === "web" && typeof window === "undefined" + ? React.useEffect + : React.useLayoutEffect; + +const styles = StyleSheet.create({ + container: { + flex: 1, + }, +}); + export default function RootLayout() { -const hasMounted = useRef(false); -const { colorScheme, isDarkColorScheme } = useColorScheme(); -const [isColorSchemeLoaded, setIsColorSchemeLoaded] = React.useState(false); + const hasMounted = useRef(false); + const { colorScheme, isDarkColorScheme } = useColorScheme(); + const [isColorSchemeLoaded, setIsColorSchemeLoaded] = React.useState(false); -useIsomorphicLayoutEffect(() => { -if (hasMounted.current) { -return; -} + useIsomorphicLayoutEffect(() => { + if (hasMounted.current) { + return; + } + setAndroidNavigationBar(colorScheme); + setIsColorSchemeLoaded(true); + hasMounted.current = true; + }, []); -setAndroidNavigationBar(colorScheme); -setIsColorSchemeLoaded(true); -hasMounted.current = true; -}, []); + if (!isColorSchemeLoaded) { + return null; + } -if (!isColorSchemeLoaded) { -return null; -} -return ( -{{#if (eq backend "convex")}} -{{#if (eq auth "clerk")}} - - - - - - + return ( + <> + {{#if (eq backend "convex")}} + {{#if (eq auth "clerk")}} + + + + + + - + + + + + + + {{else if (eq auth "better-auth")}} + + + + + + + - - - - -{{else if (eq auth "better-auth")}} - - - - - - - - - - - -{{else}} - - - - - - - - - - - -{{/if}} -{{else}} -{{#unless (eq api "none")}} - - - - - + + + + {{else}} + + + + + + + + + + + + {{/if}} + {{else}} + {{#unless (eq api "none")}} + + + + + + + + + + + + {{else}} + + + + - - - - - -{{else}} - - - - - - - - - -{{/unless}} -{{/if}} -); -} - -const useIsomorphicLayoutEffect = -Platform.OS === "web" && typeof window === "undefined" -? React.useEffect -: React.useLayoutEffect; - -const styles = StyleSheet.create({ -container: { -flex: 1, -}, -}); \ No newline at end of file + + + + + {{/unless}} + {{/if}} + + ); +} \ No newline at end of file diff --git a/apps/cli/templates/frontend/native/bare/components/container.tsx.hbs b/apps/cli/templates/frontend/native/bare/components/container.tsx.hbs index cd9a9da7c..1e491fad8 100644 --- a/apps/cli/templates/frontend/native/bare/components/container.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/components/container.tsx.hbs @@ -1,7 +1,8 @@ import React from "react"; -import { SafeAreaView, StyleSheet } from "react-native-safe-area-context"; +import { SafeAreaView } from "react-native-safe-area-context"; import { useColorScheme } from "@/lib/use-color-scheme"; import { NAV_THEME } from "@/lib/constants"; +import { StyleSheet } from "react-native"; export function Container({ children }: { children: React.ReactNode }) { const { colorScheme } = useColorScheme(); diff --git a/apps/cli/templates/frontend/native/bare/components/header-button.tsx.hbs b/apps/cli/templates/frontend/native/bare/components/header-button.tsx.hbs index d703d1923..733e5e858 100644 --- a/apps/cli/templates/frontend/native/bare/components/header-button.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/components/header-button.tsx.hbs @@ -29,7 +29,7 @@ export const HeaderButton = forwardRef< name="info-circle" size={20} color={theme.text} - style={{ + style=\{{ opacity: pressed ? 0.7 : 1, }} /> diff --git a/apps/cli/templates/frontend/native/uniwind/app.json.hbs b/apps/cli/templates/frontend/native/uniwind/app.json.hbs index 013c10136..a54d40243 100644 --- a/apps/cli/templates/frontend/native/uniwind/app.json.hbs +++ b/apps/cli/templates/frontend/native/uniwind/app.json.hbs @@ -1,16 +1,19 @@ { - "expo": { - "scheme": "{{projectName}}", - "userInterfaceStyle": "automatic", - "orientation": "default", - "web": { - "bundler": "metro" - }, - "name": "{{projectName}}", - "slug": "{{projectName}}", - "plugins": [ - "expo-font" - ] - } + "expo": { + "scheme": "{{projectName}}", + "userInterfaceStyle": "automatic", + "orientation": "default", + "web": { + "bundler": "metro" + }, + "name": "{{projectName}}", + "slug": "{{projectName}}", + "plugins": [ + "expo-font" + ], + "experiments": { + "typedRoutes": true, + "reactCompiler": true + } + } } - From 2b547ed50204eb05b0f9616062d92c515be6fcc4 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Sun, 9 Nov 2025 23:06:24 +0530 Subject: [PATCH 3/5] fix --- .cursor/rules/better-t-stack-repo.mdc | 4 +- .../native/bare/components/sign-in.tsx.hbs | 22 +- .../native/bare/components/sign-up.tsx.hbs | 22 +- .../native/bare/app/(drawer)/index.tsx.hbs | 186 ++++++++ .../native/bare/components/sign-in.tsx.hbs | 74 ++-- .../native/bare/components/sign-up.tsx.hbs | 27 +- .../unistyles/app/(drawer)/index.tsx.hbs | 10 +- .../unistyles/components/sign-in.tsx.hbs | 5 + .../unistyles/components/sign-up.tsx.hbs | 5 + .../native/uniwind/app/(drawer)/index.tsx.hbs | 196 ++++----- .../native/uniwind/components/sign-in.tsx.hbs | 129 +++--- .../native/uniwind/components/sign-up.tsx.hbs | 178 ++++---- .../ai/native/bare/app/(drawer)/ai.tsx.hbs | 40 +- .../native/bare/app/(drawer)/todos.tsx.hbs | 42 +- .../bare/app/(drawer)/(tabs)/index.tsx.hbs | 8 +- .../bare/app/(drawer)/(tabs)/two.tsx.hbs | 8 +- .../native/bare/app/(drawer)/index.tsx.hbs | 405 +++++++++--------- .../native/bare/app/+not-found.tsx.hbs | 16 +- .../frontend/native/bare/app/modal.tsx.hbs | 9 +- .../frontend/native/bare/babel.config.js.hbs | 12 - .../bare/components/header-button.tsx.hbs | 1 - 21 files changed, 757 insertions(+), 642 deletions(-) create mode 100644 apps/cli/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs delete mode 100644 apps/cli/templates/frontend/native/bare/babel.config.js.hbs diff --git a/.cursor/rules/better-t-stack-repo.mdc b/.cursor/rules/better-t-stack-repo.mdc index 4141758db..17fef7df7 100644 --- a/.cursor/rules/better-t-stack-repo.mdc +++ b/.cursor/rules/better-t-stack-repo.mdc @@ -1,10 +1,10 @@ --- alwaysApply: true --- - - Always use functional programming; avoid object-oriented programming. - Define functions using the standard function declaration syntax, not arrow functions. - Do not include emojis. - Use TypeScript type aliases instead of interface declarations. - In Handlebars templates, avoid generic if/else blocks. Write explicit conditions, such as: use if (eq orm "prisma") for Prisma, and else if (eq orm "drizzle") for Drizzle. -- Do not use explicit return types \ No newline at end of file +- Do not use explicit return types +- escape the '{{' in hbs templates like '\{{' \ No newline at end of file diff --git a/apps/cli/templates/auth/better-auth/convex/native/bare/components/sign-in.tsx.hbs b/apps/cli/templates/auth/better-auth/convex/native/bare/components/sign-in.tsx.hbs index 68b206fe4..a204392d6 100644 --- a/apps/cli/templates/auth/better-auth/convex/native/bare/components/sign-in.tsx.hbs +++ b/apps/cli/templates/auth/better-auth/convex/native/bare/components/sign-in.tsx.hbs @@ -90,44 +90,36 @@ function SignIn() { const styles = StyleSheet.create({ card: { - marginTop: 24, + marginTop: 16, padding: 16, - borderRadius: 12, borderWidth: 1, }, title: { fontSize: 18, - fontWeight: "600", - marginBottom: 16, + fontWeight: "bold", + marginBottom: 12, }, errorContainer: { - marginBottom: 16, - padding: 12, - borderRadius: 8, + marginBottom: 12, + padding: 8, }, errorText: { fontSize: 14, }, input: { borderWidth: 1, - borderRadius: 8, - paddingVertical: 12, - paddingHorizontal: 16, + padding: 12, fontSize: 16, marginBottom: 12, }, button: { - paddingVertical: 12, - paddingHorizontal: 16, - borderRadius: 8, + padding: 12, alignItems: "center", justifyContent: "center", - marginTop: 4, }, buttonText: { color: "#ffffff", fontSize: 16, - fontWeight: "500", }, }); diff --git a/apps/cli/templates/auth/better-auth/convex/native/bare/components/sign-up.tsx.hbs b/apps/cli/templates/auth/better-auth/convex/native/bare/components/sign-up.tsx.hbs index 526cf9ec0..b3a5d1fd4 100644 --- a/apps/cli/templates/auth/better-auth/convex/native/bare/components/sign-up.tsx.hbs +++ b/apps/cli/templates/auth/better-auth/convex/native/bare/components/sign-up.tsx.hbs @@ -101,44 +101,36 @@ function SignUp() { const styles = StyleSheet.create({ card: { - marginTop: 24, + marginTop: 16, padding: 16, - borderRadius: 12, borderWidth: 1, }, title: { fontSize: 18, - fontWeight: "600", - marginBottom: 16, + fontWeight: "bold", + marginBottom: 12, }, errorContainer: { - marginBottom: 16, - padding: 12, - borderRadius: 8, + marginBottom: 12, + padding: 8, }, errorText: { fontSize: 14, }, input: { borderWidth: 1, - borderRadius: 8, - paddingVertical: 12, - paddingHorizontal: 16, + padding: 12, fontSize: 16, marginBottom: 12, }, button: { - paddingVertical: 12, - paddingHorizontal: 16, - borderRadius: 8, + padding: 12, alignItems: "center", justifyContent: "center", - marginTop: 4, }, buttonText: { color: "#ffffff", fontSize: 16, - fontWeight: "500", }, }); diff --git a/apps/cli/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs b/apps/cli/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs new file mode 100644 index 000000000..f2edb1070 --- /dev/null +++ b/apps/cli/templates/auth/better-auth/native/bare/app/(drawer)/index.tsx.hbs @@ -0,0 +1,186 @@ +import { View, Text, ScrollView, TouchableOpacity, StyleSheet } from "react-native"; +import { Container } from "@/components/container"; +import { useColorScheme } from "@/lib/use-color-scheme"; +import { NAV_THEME } from "@/lib/constants"; +import { authClient } from "@/lib/auth-client"; +import { SignIn } from "@/components/sign-in"; +import { SignUp } from "@/components/sign-up"; +{{#if (eq api "orpc")}} +import { useQuery } from "@tanstack/react-query"; +import { queryClient, orpc } from "@/utils/orpc"; +{{/if}} +{{#if (eq api "trpc")}} +import { useQuery } from "@tanstack/react-query"; +import { queryClient, trpc } from "@/utils/trpc"; +{{/if}} + +export default function Home() { +const { colorScheme } = useColorScheme(); +const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; +{{#if (eq api "orpc")}} +const healthCheck = useQuery(orpc.healthCheck.queryOptions()); +const privateData = useQuery(orpc.privateData.queryOptions()); +const isConnected = healthCheck?.data === "OK"; +const isLoading = healthCheck?.isLoading; +{{/if}} +{{#if (eq api "trpc")}} +const healthCheck = useQuery(trpc.healthCheck.queryOptions()); +const privateData = useQuery(trpc.privateData.queryOptions()); +const isConnected = healthCheck?.data === "OK"; +const isLoading = healthCheck?.isLoading; +{{/if}} +const { data: session } = authClient.useSession(); + +return ( + + + + + BETTER T STACK + + + {session?.user ? ( + + + + Welcome, {session.user.name} + + + + {session.user.email} + + { + authClient.signOut(); + {{#if (eq api "orpc")}} + queryClient.invalidateQueries(); + {{/if}} + {{#if (eq api "trpc")}} + queryClient.invalidateQueries(); + {{/if}} + }} + > + Sign Out + + + ) : null} + + {{#unless (eq api "none")}} + + + System Status + + + + + + {{#if (eq api "orpc")}}ORPC{{else}}TRPC{{/if}} Backend + + + {isLoading + ? "Checking connection..." + : isConnected + ? "Connected to API" + : "API Disconnected"} + + + + + + + + Private Data + + {privateData && ( + + {privateData.data?.message} + + )} + + {{/unless}} + + {!session?.user && ( + <> + + + + )} + + + +); +} + +const styles = StyleSheet.create({ +scrollView: { +flex: 1, +}, +content: { +padding: 16, +}, +title: { +fontSize: 24, +fontWeight: "bold", +marginBottom: 16, +}, +userCard: { +marginBottom: 16, +padding: 16, +borderWidth: 1, +}, +userHeader: { +marginBottom: 8, +}, +userText: { +fontSize: 16, +}, +userName: { +fontWeight: "bold", +}, +userEmail: { +fontSize: 14, +marginBottom: 12, +}, +signOutButton: { +padding: 12, +}, +signOutText: { +color: "#ffffff", +}, +statusCard: { +marginBottom: 16, +padding: 16, +borderWidth: 1, +}, +cardTitle: { +fontSize: 16, +fontWeight: "bold", +marginBottom: 12, +}, +statusRow: { +flexDirection: "row", +alignItems: "center", +gap: 8, +}, +statusIndicator: { +height: 8, +width: 8, +}, +statusContent: { +flex: 1, +}, +statusTitle: { +fontSize: 14, +fontWeight: "bold", +}, +statusText: { +fontSize: 12, +}, +privateDataCard: { +marginBottom: 16, +padding: 16, +borderWidth: 1, +}, +privateDataText: { +fontSize: 14, +}, +}); \ No newline at end of file diff --git a/apps/cli/templates/auth/better-auth/native/bare/components/sign-in.tsx.hbs b/apps/cli/templates/auth/better-auth/native/bare/components/sign-in.tsx.hbs index 36ee65f21..22dc10bf2 100644 --- a/apps/cli/templates/auth/better-auth/native/bare/components/sign-in.tsx.hbs +++ b/apps/cli/templates/auth/better-auth/native/bare/components/sign-in.tsx.hbs @@ -20,19 +20,22 @@ import { NAV_THEME } from "@/lib/constants"; function SignIn() { const { colorScheme } = useColorScheme(); const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; -const [email, setEmail] = useState(""); -const [password, setPassword] = useState(""); +const [form, setForm] = useState({ email: "", password: "" }); const [isLoading, setIsLoading] = useState(false); const [error, setError] = useState(null); + function handleFormChange(field: "email" | "password", value: string) { + setForm(prev => ({ ...prev, [field]: value })); + } + async function handleLogin() { setIsLoading(true); setError(null); await authClient.signIn.email( { - email, - password, + email: form.email, + password: form.password, }, { onError(error) { @@ -40,9 +43,13 @@ const [error, setError] = useState(null); setIsLoading(false); }, onSuccess() { - setEmail(""); - setPassword(""); + setForm({ email: "", password: "" }); + {{#if (eq api "orpc")}} queryClient.refetchQueries(); + {{/if}} + {{#if (eq api "trpc")}} + queryClient.refetchQueries(); + {{/if}} }, onFinished() { setIsLoading(false); @@ -61,66 +68,63 @@ const [error, setError] = useState(null); ) : null} - + handleFormChange("email", value)} + keyboardType="email-address" + autoCapitalize="none" + /> - + handleFormChange("password", value)} + secureTextEntry + /> - - {isLoading ? ( - - ) : ( - Sign In - )} - + + {isLoading ? ( + + ) : ( + Sign In + )} + ); } const styles = StyleSheet.create({ card: { - marginTop: 24, + marginTop: 16, padding: 16, - borderRadius: 12, borderWidth: 1, }, title: { fontSize: 18, - fontWeight: "600", - marginBottom: 16, + fontWeight: "bold", + marginBottom: 12, }, errorContainer: { - marginBottom: 16, - padding: 12, - borderRadius: 8, + marginBottom: 12, + padding: 8, }, errorText: { fontSize: 14, }, input: { borderWidth: 1, - borderRadius: 8, - paddingVertical: 12, - paddingHorizontal: 16, + padding: 12, fontSize: 16, marginBottom: 12, }, button: { - paddingVertical: 12, - paddingHorizontal: 16, - borderRadius: 8, + padding: 12, alignItems: "center", justifyContent: "center", - marginTop: 4, }, buttonText: { color: "#ffffff", fontSize: 16, - fontWeight: "500", }, }); diff --git a/apps/cli/templates/auth/better-auth/native/bare/components/sign-up.tsx.hbs b/apps/cli/templates/auth/better-auth/native/bare/components/sign-up.tsx.hbs index e48e318ba..358293929 100644 --- a/apps/cli/templates/auth/better-auth/native/bare/components/sign-up.tsx.hbs +++ b/apps/cli/templates/auth/better-auth/native/bare/components/sign-up.tsx.hbs @@ -45,7 +45,12 @@ function SignUp() { setName(""); setEmail(""); setPassword(""); + {{#if (eq api "orpc")}} queryClient.refetchQueries(); + {{/if}} + {{#if (eq api "trpc")}} + queryClient.refetchQueries(); + {{/if}} }, onFinished() { setIsLoading(false); @@ -108,44 +113,36 @@ function SignUp() { const styles = StyleSheet.create({ card: { - marginTop: 24, + marginTop: 16, padding: 16, - borderRadius: 12, borderWidth: 1, }, title: { fontSize: 18, - fontWeight: "600", - marginBottom: 16, + fontWeight: "bold", + marginBottom: 12, }, errorContainer: { - marginBottom: 16, - padding: 12, - borderRadius: 8, + marginBottom: 12, + padding: 8, }, errorText: { fontSize: 14, }, input: { borderWidth: 1, - borderRadius: 8, - paddingVertical: 12, - paddingHorizontal: 16, + padding: 12, fontSize: 16, marginBottom: 12, }, button: { - paddingVertical: 12, - paddingHorizontal: 16, - borderRadius: 8, + padding: 12, alignItems: "center", justifyContent: "center", - marginTop: 4, }, buttonText: { color: "#ffffff", fontSize: 16, - fontWeight: "500", }, }); diff --git a/apps/cli/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs b/apps/cli/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs index b7c6193c6..184ee1219 100644 --- a/apps/cli/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs +++ b/apps/cli/templates/auth/better-auth/native/unistyles/app/(drawer)/index.tsx.hbs @@ -1,5 +1,4 @@ import { authClient } from "@/lib/auth-client"; -import { useQuery } from "@tanstack/react-query"; import { ScrollView, Text, TouchableOpacity, View } from "react-native"; import { StyleSheet } from "react-native-unistyles"; @@ -7,9 +6,11 @@ import { Container } from "@/components/container"; import { SignIn } from "@/components/sign-in"; import { SignUp } from "@/components/sign-up"; {{#if (eq api "orpc")}} +import { useQuery } from "@tanstack/react-query"; import { queryClient, orpc } from "@/utils/orpc"; {{/if}} {{#if (eq api "trpc")}} +import { useQuery } from "@tanstack/react-query"; import { queryClient, trpc } from "@/utils/trpc"; {{/if}} @@ -43,13 +44,19 @@ export default function Home() { style={styles.signOutButton} onPress={() => { authClient.signOut(); + {{#if (eq api "orpc")}} + queryClient.invalidateQueries(); + {{/if}} + {{#if (eq api "trpc")}} queryClient.invalidateQueries(); + {{/if}} }} > Sign Out ) : null} + {{#unless (eq api "none")}} API Status @@ -80,6 +87,7 @@ export default function Home() { )} + {{/unless}} {!session?.user && ( <> diff --git a/apps/cli/templates/auth/better-auth/native/unistyles/components/sign-in.tsx.hbs b/apps/cli/templates/auth/better-auth/native/unistyles/components/sign-in.tsx.hbs index c63395bd6..721584309 100644 --- a/apps/cli/templates/auth/better-auth/native/unistyles/components/sign-in.tsx.hbs +++ b/apps/cli/templates/auth/better-auth/native/unistyles/components/sign-in.tsx.hbs @@ -38,7 +38,12 @@ export function SignIn() { onSuccess: () => { setEmail(""); setPassword(""); + {{#if (eq api "orpc")}} queryClient.refetchQueries(); + {{/if}} + {{#if (eq api "trpc")}} + queryClient.refetchQueries(); + {{/if}} }, onFinished: () => { setIsLoading(false); diff --git a/apps/cli/templates/auth/better-auth/native/unistyles/components/sign-up.tsx.hbs b/apps/cli/templates/auth/better-auth/native/unistyles/components/sign-up.tsx.hbs index 67f0a1baa..502e49d73 100644 --- a/apps/cli/templates/auth/better-auth/native/unistyles/components/sign-up.tsx.hbs +++ b/apps/cli/templates/auth/better-auth/native/unistyles/components/sign-up.tsx.hbs @@ -41,7 +41,12 @@ export function SignUp() { setName(""); setEmail(""); setPassword(""); + {{#if (eq api "orpc")}} queryClient.refetchQueries(); + {{/if}} + {{#if (eq api "trpc")}} + queryClient.refetchQueries(); + {{/if}} }, onFinished: () => { setIsLoading(false); diff --git a/apps/cli/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs b/apps/cli/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs index 37a6be3bc..1288bb96d 100644 --- a/apps/cli/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs +++ b/apps/cli/templates/auth/better-auth/native/uniwind/app/(drawer)/index.tsx.hbs @@ -1,123 +1,123 @@ import { Text, View, Pressable } from "react-native"; import { Container } from "@/components/container"; import { authClient } from "@/lib/auth-client"; -import { useQuery } from "@tanstack/react-query"; import { Ionicons } from "@expo/vector-icons"; import { Card, Chip, useThemeColor } from "heroui-native"; import { SignIn } from "@/components/sign-in"; import { SignUp } from "@/components/sign-up"; {{#if (eq api "orpc")}} +import { useQuery } from "@tanstack/react-query"; import { queryClient, orpc } from "@/utils/orpc"; {{/if}} {{#if (eq api "trpc")}} +import { useQuery } from "@tanstack/react-query"; 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(); - - const mutedColor = useThemeColor("muted"); - const successColor = useThemeColor("success"); - const dangerColor = useThemeColor("danger"); - const foregroundColor = useThemeColor("foreground"); - - const isConnected = healthCheck?.data === "OK"; - const isLoading = healthCheck?.isLoading; +{{#if (eq api "orpc")}} +const healthCheck = useQuery(orpc.healthCheck.queryOptions()); +const privateData = useQuery(orpc.privateData.queryOptions()); +const isConnected = healthCheck?.data === "OK"; +const isLoading = healthCheck?.isLoading; +{{/if}} +{{#if (eq api "trpc")}} +const healthCheck = useQuery(trpc.healthCheck.queryOptions()); +const privateData = useQuery(trpc.privateData.queryOptions()); +const isConnected = healthCheck?.data === "OK"; +const isLoading = healthCheck?.isLoading; +{{/if}} +const { data: session } = authClient.useSession(); - return ( - - - - BETTER T STACK - - +const mutedColor = useThemeColor("muted"); +const successColor = useThemeColor("success"); +const dangerColor = useThemeColor("danger"); +const foregroundColor = useThemeColor("foreground"); - {session?.user ? ( - - - Welcome, {session.user.name} - - - {session.user.email} - - { - authClient.signOut(); - queryClient.invalidateQueries(); - }} - > - Sign Out - - - ) : null} +return ( + + + + BETTER T STACK + + - - - System Status - - {isConnected ? "LIVE" : "OFFLINE"} - - + {session?.user ? ( + + + Welcome, {session.user.name} + + + {session.user.email} + + { + authClient.signOut(); + {{#if (eq api "orpc")}} + queryClient.invalidateQueries(); + {{/if}} + {{#if (eq api "trpc")}} + queryClient.invalidateQueries(); + {{/if}} + }} + > + Sign Out + + + ) : null} - - - - - - {{#if (eq api "orpc")}}ORPC{{else}}TRPC{{/if}} Backend - - - {isLoading - ? "Checking connection..." - : isConnected - ? "Connected to API" - : "API Disconnected"} - - - {isLoading && ( - - )} - {!isLoading && isConnected && ( - - )} - {!isLoading && !isConnected && ( - - )} - - - + {{#unless (eq api "none")}} + + + System Status + + {isConnected ? "LIVE" : "OFFLINE"} + + - - Private Data - {privateData && ( + + + + + + {{#if (eq api "orpc")}}ORPC{{else}}TRPC{{/if}} Backend + - {privateData.data?.message} + {isLoading + ? "Checking connection..." + : isConnected + ? "Connected to API" + : "API Disconnected"} + + {isLoading && ( + + )} + {!isLoading && isConnected && ( + )} - + {!isLoading && !isConnected && ( + + )} + + + - {!session?.user && ( - <> - - - - )} - - ); -} + + Private Data + {privateData && ( + + {privateData.data?.message} + + )} + + {{/unless}} + {!session?.user && ( + <> + + + + )} + +); +} \ No newline at end of file diff --git a/apps/cli/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs b/apps/cli/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs index 2c428fab5..f6ab4d43f 100644 --- a/apps/cli/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs +++ b/apps/cli/templates/auth/better-auth/native/uniwind/components/sign-in.tsx.hbs @@ -7,19 +7,19 @@ import { queryClient } from "@/utils/orpc"; {{/if}} import { useState } from "react"; import { - ActivityIndicator, - Text, - TextInput, - Pressable, - View, +ActivityIndicator, +Text, +TextInput, +Pressable, +View, } from "react-native"; import { Card, useThemeColor } from "heroui-native"; function SignIn() { - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); +const [email, setEmail] = useState(""); +const [password, setPassword] = useState(""); +const [isLoading, setIsLoading] = useState(false); +const [error, setError] = useState(null); const mutedColor = useThemeColor("muted"); const accentColor = useThemeColor("accent"); @@ -27,73 +27,64 @@ function SignIn() { const dangerColor = useThemeColor("danger"); async function handleLogin() { - setIsLoading(true); - setError(null); + setIsLoading(true); + setError(null); - await authClient.signIn.email( - { - email, - password, - }, - { - onError(error) { - setError(error.error?.message || "Failed to sign in"); - setIsLoading(false); - }, - onSuccess() { - setEmail(""); - setPassword(""); - queryClient.refetchQueries(); - }, - onFinished() { - setIsLoading(false); - }, - } - ); + await authClient.signIn.email( + { + email, + password, + }, + { + onError(error) { + setError(error.error?.message || "Failed to sign in"); + setIsLoading(false); + }, + onSuccess() { + setEmail(""); + setPassword(""); + {{#if (eq api "orpc")}} + queryClient.refetchQueries(); + {{/if}} + {{#if (eq api "trpc")}} + queryClient.refetchQueries(); + {{/if}} + }, + onFinished() { + setIsLoading(false); + }, + } + ); } return ( - - Sign In + + Sign In - {error ? ( - - {error} - - ) : null} + {error ? ( + + {error} + + ) : null} - + - + - - {isLoading ? ( - - ) : ( - Sign In - )} - - + + {isLoading ? ( + + ) : ( + Sign In + )} + + ); -} + } -export { SignIn }; \ No newline at end of file + export { SignIn }; \ No newline at end of file diff --git a/apps/cli/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs b/apps/cli/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs index 3d22b08d2..0e529d3a7 100644 --- a/apps/cli/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs +++ b/apps/cli/templates/auth/better-auth/native/uniwind/components/sign-up.tsx.hbs @@ -7,57 +7,62 @@ import { queryClient } from "@/utils/orpc"; {{/if}} import { useState } from "react"; import { - ActivityIndicator, - Text, - TextInput, - Pressable, - View, +ActivityIndicator, +Text, +TextInput, +Pressable, +View, } from "react-native"; import { Card, useThemeColor } from "heroui-native"; function signUpHandler({ - name, - email, - password, - setError, - setIsLoading, - setName, - setEmail, - setPassword, +name, +email, +password, +setError, +setIsLoading, +setName, +setEmail, +setPassword, }) { - setIsLoading(true); - setError(null); +setIsLoading(true); +setError(null); - authClient.signUp.email( - { - name, - email, - password, - }, - { - onError(error) { - setError(error.error?.message || "Failed to sign up"); - setIsLoading(false); - }, - onSuccess() { - setName(""); - setEmail(""); - setPassword(""); - queryClient.refetchQueries(); - }, - onFinished() { - setIsLoading(false); - }, - } - ); +authClient.signUp.email( +{ +name, +email, +password, +}, +{ +onError(error) { +setError(error.error?.message || "Failed to sign up"); +setIsLoading(false); +}, +onSuccess() { +setName(""); +setEmail(""); +setPassword(""); +{{#if (eq api "orpc")}} +queryClient.refetchQueries(); +{{/if}} +{{#if (eq api "trpc")}} +queryClient.refetchQueries(); +{{/if}} +}, +onFinished() { +setIsLoading(false); +}, +} +); } export function SignUp() { - const [name, setName] = useState(""); - const [email, setEmail] = useState(""); - const [password, setPassword] = useState(""); - const [isLoading, setIsLoading] = useState(false); - const [error, setError] = useState(null); +const [name, setName] = useState(""); +const [email, setEmail] = useState(""); +const [password, setPassword] = useState(""); +const [isLoading, setIsLoading] = useState(false); +const [error, setError] = useState(null); const mutedColor = useThemeColor("muted"); const accentColor = useThemeColor("accent"); @@ -65,66 +70,47 @@ export function SignUp() { const dangerColor = useThemeColor("danger"); function handlePress() { - signUpHandler({ - name, - email, - password, - setError, - setIsLoading, - setName, - setEmail, - setPassword, - }); + signUpHandler({ + name, + email, + password, + setError, + setIsLoading, + setName, + setEmail, + setPassword, + }); } return ( - - Create Account + + Create Account - {error && ( - - {error} - - )} + {error && ( + + {error} + + )} - + - + - + - - {isLoading ? ( - - ) : ( - Sign Up - )} - - + + {isLoading ? ( + + ) : ( + Sign Up + )} + + ); -} \ No newline at end of file + } \ No newline at end of file diff --git a/apps/cli/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs b/apps/cli/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs index 6e436d362..7a754c224 100644 --- a/apps/cli/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs +++ b/apps/cli/templates/examples/ai/native/bare/app/(drawer)/ai.tsx.hbs @@ -194,19 +194,18 @@ const styles = StyleSheet.create({ }, content: { flex: 1, - paddingHorizontal: 16, - paddingTop: 24, + padding: 16, }, header: { - marginBottom: 24, + marginBottom: 16, }, headerTitle: { - fontSize: 28, + fontSize: 20, fontWeight: "bold", - marginBottom: 8, + marginBottom: 4, }, headerSubtitle: { - fontSize: 16, + fontSize: 14, }, scrollView: { flex: 1, @@ -218,22 +217,21 @@ const styles = StyleSheet.create({ alignItems: "center", }, emptyText: { - fontSize: 18, + fontSize: 16, textAlign: "center", }, messagesList: { - gap: 12, + gap: 8, paddingBottom: 16, }, messageCard: { borderWidth: 1, - borderRadius: 12, padding: 12, maxWidth: "80%", }, messageRole: { fontSize: 12, - fontWeight: "600", + fontWeight: "bold", marginBottom: 4, }, messageParts: { @@ -245,7 +243,7 @@ const styles = StyleSheet.create({ }, inputContainer: { borderTopWidth: 1, - paddingTop: 16, + paddingTop: 12, }, inputRow: { flexDirection: "row", @@ -255,16 +253,13 @@ const styles = StyleSheet.create({ input: { flex: 1, borderWidth: 1, - borderRadius: 8, - paddingHorizontal: 12, - paddingVertical: 8, - fontSize: 16, - minHeight: 40, - maxHeight: 120, + padding: 8, + fontSize: 14, + minHeight: 36, + maxHeight: 100, }, sendButton: { - padding: 10, - borderRadius: 8, + padding: 8, justifyContent: "center", alignItems: "center", }, @@ -272,16 +267,15 @@ const styles = StyleSheet.create({ flex: 1, justifyContent: "center", alignItems: "center", - paddingHorizontal: 16, + padding: 16, }, errorCard: { borderWidth: 1, - borderRadius: 12, padding: 16, }, errorTitle: { - fontSize: 18, - fontWeight: "600", + fontSize: 16, + fontWeight: "bold", marginBottom: 8, textAlign: "center", }, diff --git a/apps/cli/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs b/apps/cli/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs index bdae56d52..d05a563b6 100644 --- a/apps/cli/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs +++ b/apps/cli/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs @@ -329,62 +329,55 @@ const styles = StyleSheet.create({ flex: 1, }, contentContainer: { - padding: 24, + padding: 16, }, header: { - marginBottom: 24, + marginBottom: 16, }, headerRow: { flexDirection: "row", alignItems: "center", justifyContent: "space-between", - marginBottom: 8, }, title: { - fontSize: 32, + fontSize: 24, fontWeight: "bold", }, badge: { - paddingHorizontal: 12, + paddingHorizontal: 8, paddingVertical: 4, - borderRadius: 12, }, badgeText: { color: "#ffffff", fontSize: 12, - fontWeight: "600", }, inputCard: { borderWidth: 1, - borderRadius: 12, - padding: 16, - marginBottom: 24, + padding: 12, + marginBottom: 16, }, inputRow: { flexDirection: "row", alignItems: "center", - gap: 12, + gap: 8, }, inputContainer: { flex: 1, }, input: { borderWidth: 1, - borderRadius: 8, - paddingVertical: 12, - paddingHorizontal: 16, + padding: 12, fontSize: 16, }, addButton: { padding: 12, - borderRadius: 8, justifyContent: "center", alignItems: "center", }, centerContainer: { alignItems: "center", justifyContent: "center", - paddingVertical: 48, + paddingVertical: 32, }, loadingText: { marginTop: 16, @@ -392,14 +385,13 @@ const styles = StyleSheet.create({ }, emptyCard: { borderWidth: 1, - borderRadius: 12, - padding: 48, + padding: 32, alignItems: "center", justifyContent: "center", }, emptyTitle: { - fontSize: 18, - fontWeight: "600", + fontSize: 16, + fontWeight: "bold", marginBottom: 8, }, emptyText: { @@ -407,12 +399,11 @@ const styles = StyleSheet.create({ textAlign: "center", }, todosList: { - gap: 12, + gap: 8, }, todoCard: { borderWidth: 1, - borderRadius: 12, - padding: 16, + padding: 12, }, todoRow: { flexDirection: "row", @@ -420,10 +411,9 @@ const styles = StyleSheet.create({ gap: 12, }, checkbox: { - width: 24, - height: 24, + width: 20, + height: 20, borderWidth: 2, - borderRadius: 4, justifyContent: "center", alignItems: "center", }, diff --git a/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/index.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/index.tsx.hbs index f5595b919..1d55bf21c 100644 --- a/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/index.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/index.tsx.hbs @@ -26,18 +26,18 @@ export default function TabOne() { const styles = StyleSheet.create({ scrollView: { flex: 1, - padding: 24, + padding: 16, }, content: { - paddingVertical: 32, + paddingVertical: 16, }, title: { - fontSize: 30, + fontSize: 24, fontWeight: "bold", marginBottom: 8, }, subtitle: { - fontSize: 18, + fontSize: 16, }, }); diff --git a/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/two.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/two.tsx.hbs index ad9642384..0ffa6a31c 100644 --- a/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/two.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/app/(drawer)/(tabs)/two.tsx.hbs @@ -26,18 +26,18 @@ export default function TabTwo() { const styles = StyleSheet.create({ scrollView: { flex: 1, - padding: 24, + padding: 16, }, content: { - paddingVertical: 32, + paddingVertical: 16, }, title: { - fontSize: 30, + fontSize: 24, fontWeight: "bold", marginBottom: 8, }, subtitle: { - fontSize: 18, + fontSize: 16, }, }); diff --git a/apps/cli/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs index 645cf3190..8e7c1a612 100644 --- a/apps/cli/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/app/(drawer)/index.tsx.hbs @@ -28,222 +28,207 @@ import { api } from "@{{ projectName }}/backend/convex/_generated/api"; {{/if}} export default function Home() { - const { colorScheme } = useColorScheme(); - const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; - {{#if (eq api "orpc")}} - const healthCheck = useQuery(orpc.healthCheck.queryOptions()); - {{/if}} - {{#if (eq api "trpc")}} - const healthCheck = useQuery(trpc.healthCheck.queryOptions()); - {{/if}} - {{#if (and (eq backend "convex") (eq auth "clerk"))}} - const { user } = useUser(); - const healthCheck = useQuery(api.healthCheck.get); - const privateData = useQuery(api.privateData.get); - {{else if (and (eq backend "convex") (eq auth "better-auth"))}} - const healthCheck = useQuery(api.healthCheck.get); - const { isAuthenticated } = useConvexAuth(); - const user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : "skip"); - {{else if (eq backend "convex")}} - const healthCheck = useQuery(api.healthCheck.get); - {{/if}} +const { colorScheme } = useColorScheme(); +const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; +{{#if (eq api "orpc")}} +const healthCheck = useQuery(orpc.healthCheck.queryOptions()); +{{/if}} +{{#if (eq api "trpc")}} +const healthCheck = useQuery(trpc.healthCheck.queryOptions()); +{{/if}} +{{#if (and (eq backend "convex") (eq auth "clerk"))}} +const { user } = useUser(); +const healthCheck = useQuery(api.healthCheck.get); +const privateData = useQuery(api.privateData.get); +{{else if (and (eq backend "convex") (eq auth "better-auth"))}} +const healthCheck = useQuery(api.healthCheck.get); +const { isAuthenticated } = useConvexAuth(); +const user = useQuery(api.auth.getCurrentUser, isAuthenticated ? {} : "skip"); +{{else if (eq backend "convex")}} +const healthCheck = useQuery(api.healthCheck.get); +{{/if}} - return ( - - - - - BETTER T STACK - +return ( + + + + + BETTER T STACK + - {{#unless (and (eq backend "convex") (eq auth "better-auth"))}} - - {{#if (eq backend "convex")}} - - - - - Convex - - - {healthCheck === undefined - ? "Checking..." - : healthCheck === "OK" - ? "Connected to API" - : "API Disconnected"} - - - - {{else}} - {{#unless (eq api "none")}} - - - - - {{#if (eq api "orpc")}}ORPC{{else}}TRPC{{/if}} - - - {healthCheck.isLoading - ? "Checking connection..." - : healthCheck.data - ? "All systems operational" - : "Service unavailable"} - - - - {{/unless}} - {{/if}} + {{#unless (and (eq backend "convex") (eq auth "better-auth"))}} + + {{#if (eq backend "convex")}} + + + + + Convex + + + {healthCheck === undefined + ? "Checking..." + : healthCheck === "OK" + ? "Connected to API" + : "API Disconnected"} + - {{/unless}} - - {{#if (and (eq backend "convex") (eq auth "clerk"))}} - - Hello {user?.emailAddresses[0].emailAddress} - Private Data: {privateData?.message} - - - - - Sign in - - - Sign up - - - - Loading... - - {{/if}} - - {{#if (and (eq backend "convex") (eq auth "better-auth"))}} - {user ? ( - - - - Welcome, {user.name} - - - - {user.email} - - { - authClient.signOut(); - }} - > - Sign Out - - - ) : null} - - - API Status + + {{else}} + {{#unless (eq api "none")}} + + + + + {{#if (eq api "orpc")}}ORPC{{else}}TRPC{{/if}} + + + {healthCheck.isLoading + ? "Checking connection..." + : healthCheck.data + ? "All systems operational" + : "Service unavailable"} - - - - {healthCheck === undefined - ? "Checking..." - : healthCheck === "OK" - ? "Connected to API" - : "API Disconnected"} - - - {!user && ( - <> - - - - )} - {{/if}} - - - ); + {{/unless}} + {{/if}} + + {{/unless}} + + {{#if (and (eq backend "convex") (eq auth "clerk"))}} + + Hello {user?.emailAddresses[0].emailAddress} + Private Data: {privateData?.message} + + + + + Sign in + + + Sign up + + + + Loading... + + {{/if}} + + {{#if (and (eq backend "convex") (eq auth "better-auth"))}} + {user ? ( + + + + Welcome, {user.name} + + + + {user.email} + + { + authClient.signOut(); + }} + > + Sign Out + + + ) : null} + + + API Status + + + + + {healthCheck === undefined + ? "Checking..." + : healthCheck === "OK" + ? "Connected to API" + : "API Disconnected"} + + + + {!user && ( + <> + + + + )} + {{/if}} + + + +); } const styles = StyleSheet.create({ - scrollView: { - flex: 1, - }, - content: { - padding: 16, - }, - title: { - fontFamily: "monospace", - fontSize: 32, - fontWeight: "bold", - marginBottom: 16, - }, - card: { - borderWidth: 1, - borderRadius: 12, - padding: 24, - marginBottom: 24, - }, - statusRow: { - flexDirection: "row", - alignItems: "center", - gap: 12, - }, - statusIndicator: { - height: 12, - width: 12, - borderRadius: 6, - }, - statusContent: { - flex: 1, - }, - statusTitle: { - fontSize: 14, - fontWeight: "500", - }, - statusText: { - fontSize: 12, - }, - userCard: { - marginBottom: 24, - padding: 16, - borderRadius: 8, - borderWidth: 1, - }, - userHeader: { - flexDirection: "row", - justifyContent: "space-between", - alignItems: "center", - marginBottom: 8, - }, - userText: { - fontSize: 16, - }, - userName: { - fontWeight: "500", - }, - userEmail: { - fontSize: 14, - marginBottom: 16, - }, - signOutButton: { - paddingVertical: 8, - paddingHorizontal: 16, - borderRadius: 6, - alignSelf: "flex-start", - }, - signOutText: { - color: "#ffffff", - fontWeight: "500", - }, - statusCard: { - marginBottom: 24, - borderRadius: 8, - borderWidth: 1, - padding: 16, - }, - statusCardTitle: { - marginBottom: 12, - fontWeight: "500", - }, -}); - +scrollView: { +flex: 1, +}, +content: { +padding: 16, +}, +title: { +fontSize: 24, +fontWeight: "bold", +marginBottom: 16, +}, +card: { +padding: 16, +marginBottom: 16, +borderWidth: 1, +}, +statusRow: { +flexDirection: "row", +alignItems: "center", +gap: 8, +}, +statusIndicator: { +height: 8, +width: 8, +}, +statusContent: { +flex: 1, +}, +statusTitle: { +fontSize: 14, +fontWeight: "bold", +}, +statusText: { +fontSize: 12, +}, +userCard: { +marginBottom: 16, +padding: 16, +borderWidth: 1, +}, +userHeader: { +marginBottom: 8, +}, +userText: { +fontSize: 16, +}, +userName: { +fontWeight: "bold", +}, +userEmail: { +fontSize: 14, +marginBottom: 12, +}, +signOutButton: { +padding: 12, +}, +signOutText: { +color: "#ffffff", +}, +statusCard: { +marginBottom: 16, +padding: 16, +borderWidth: 1, +}, +statusCardTitle: { +marginBottom: 8, +fontWeight: "bold", +}, +}); \ No newline at end of file diff --git a/apps/cli/templates/frontend/native/bare/app/+not-found.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/+not-found.tsx.hbs index aa742386c..100e1dd59 100644 --- a/apps/cli/templates/frontend/native/bare/app/+not-found.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/app/+not-found.tsx.hbs @@ -38,32 +38,28 @@ const styles = StyleSheet.create({ flex: 1, justifyContent: "center", alignItems: "center", - padding: 24, + padding: 16, }, content: { alignItems: "center", }, emoji: { - fontSize: 64, + fontSize: 48, marginBottom: 16, }, title: { - fontSize: 24, + fontSize: 20, fontWeight: "bold", marginBottom: 8, textAlign: "center", }, subtitle: { - fontSize: 16, + fontSize: 14, textAlign: "center", - marginBottom: 32, - maxWidth: 300, + marginBottom: 24, }, link: { - fontWeight: "500", - paddingHorizontal: 24, - paddingVertical: 12, - borderRadius: 8, + padding: 12, }, }); diff --git a/apps/cli/templates/frontend/native/bare/app/modal.tsx.hbs b/apps/cli/templates/frontend/native/bare/app/modal.tsx.hbs index 601eb7588..e568296c8 100644 --- a/apps/cli/templates/frontend/native/bare/app/modal.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/app/modal.tsx.hbs @@ -21,16 +21,13 @@ export default function Modal() { const styles = StyleSheet.create({ container: { flex: 1, - padding: 24, + padding: 16, }, header: { - flexDirection: "row", - alignItems: "center", - justifyContent: "space-between", - marginBottom: 32, + marginBottom: 16, }, title: { - fontSize: 24, + fontSize: 20, fontWeight: "bold", }, }); diff --git a/apps/cli/templates/frontend/native/bare/babel.config.js.hbs b/apps/cli/templates/frontend/native/bare/babel.config.js.hbs deleted file mode 100644 index 5e34abbcc..000000000 --- a/apps/cli/templates/frontend/native/bare/babel.config.js.hbs +++ /dev/null @@ -1,12 +0,0 @@ -module.exports = (api) => { - api.cache(true); - const plugins = []; - - plugins.push("react-native-worklets/plugin"); - - return { - presets: ["babel-preset-expo"], - plugins, - }; -}; - diff --git a/apps/cli/templates/frontend/native/bare/components/header-button.tsx.hbs b/apps/cli/templates/frontend/native/bare/components/header-button.tsx.hbs index 733e5e858..397539807 100644 --- a/apps/cli/templates/frontend/native/bare/components/header-button.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/components/header-button.tsx.hbs @@ -42,7 +42,6 @@ const styles = StyleSheet.create({ button: { padding: 8, marginRight: 8, - borderRadius: 8, }, }); From 9527d4c02af698f22133625d04d6b6c002d7b14b Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Sun, 9 Nov 2025 23:37:04 +0530 Subject: [PATCH 4/5] update stack builder --- apps/cli/src/helpers/core/auth-setup.ts | 5 ++++- apps/cli/src/utils/compatibility-rules.ts | 3 ++- apps/cli/test/api.test.ts | 6 ++++- .../src/app/(home)/new/_components/utils.ts | 22 +++++++++---------- apps/web/src/lib/constant.ts | 21 +++++++++++++----- 5 files changed, 37 insertions(+), 20 deletions(-) diff --git a/apps/cli/src/helpers/core/auth-setup.ts b/apps/cli/src/helpers/core/auth-setup.ts index f1c3c61fd..bf1c543aa 100644 --- a/apps/cli/src/helpers/core/auth-setup.ts +++ b/apps/cli/src/helpers/core/auth-setup.ts @@ -102,7 +102,10 @@ export async function setupAuth(config: ProjectConfig) { const hasNativeBare = frontend.includes("native-bare"); const hasNativeUniwind = frontend.includes("native-uniwind"); const hasUnistyles = frontend.includes("native-unistyles"); - if (nativeDirExists && (hasNativeBare || hasNativeUniwind || hasUnistyles)) { + if ( + nativeDirExists && + (hasNativeBare || hasNativeUniwind || hasUnistyles) + ) { await addPackageDependency({ dependencies: [ "better-auth", diff --git a/apps/cli/src/utils/compatibility-rules.ts b/apps/cli/src/utils/compatibility-rules.ts index b4c51515e..742bc7ce4 100644 --- a/apps/cli/src/utils/compatibility-rules.ts +++ b/apps/cli/src/utils/compatibility-rules.ts @@ -24,7 +24,8 @@ export function splitFrontends(values: Frontend[] = []): { } { const web = values.filter((f) => isWebFrontend(f)); const native = values.filter( - (f) => f === "native-bare" || f === "native-uniwind" || f === "native-unistyles", + (f) => + f === "native-bare" || f === "native-uniwind" || f === "native-unistyles", ); return { web, native }; } diff --git a/apps/cli/test/api.test.ts b/apps/cli/test/api.test.ts index f6973296e..385098f4b 100644 --- a/apps/cli/test/api.test.ts +++ b/apps/cli/test/api.test.ts @@ -49,7 +49,11 @@ describe("API Configurations", () => { }); it("should work with tRPC + native frontends", async () => { - const nativeFrontends = ["native-bare", "native-uniwind", "native-unistyles"]; + const nativeFrontends = [ + "native-bare", + "native-uniwind", + "native-unistyles", + ]; for (const frontend of nativeFrontends) { const result = await runTRPCTest({ diff --git a/apps/web/src/app/(home)/new/_components/utils.ts b/apps/web/src/app/(home)/new/_components/utils.ts index 1c0086868..0cc76f7b7 100644 --- a/apps/web/src/app/(home)/new/_components/utils.ts +++ b/apps/web/src/app/(home)/new/_components/utils.ts @@ -105,7 +105,7 @@ export const analyzeStackCompatibility = ( ), ) || nextStack.nativeFrontend.some((f) => - ["native-nativewind", "native-unistyles"].includes(f), + ["native-bare", "native-uniwind", "native-unistyles"].includes(f), ); const hasBetterAuthCompatibleFrontend = @@ -113,7 +113,7 @@ export const analyzeStackCompatibility = ( ["tanstack-router", "tanstack-start", "next"].includes(f), ) || nextStack.nativeFrontend.some((f) => - ["native-nativewind", "native-unistyles"].includes(f), + ["native-bare", "native-uniwind", "native-unistyles"].includes(f), ); if (nextStack.auth === "clerk" && !hasClerkCompatibleFrontend) { @@ -882,7 +882,7 @@ export const analyzeStackCompatibility = ( ].includes(f), ) || nextStack.nativeFrontend.some((f) => - ["native-nativewind", "native-unistyles"].includes(f), + ["native-bare", "native-uniwind", "native-unistyles"].includes(f), ); if (!hasClerkCompatibleFrontend) { @@ -911,12 +911,12 @@ export const analyzeStackCompatibility = ( ["tanstack-router", "tanstack-start", "next"].includes(f), ) || nextStack.nativeFrontend.some((f) => - ["native-nativewind", "native-unistyles"].includes(f), + ["native-bare", "native-uniwind", "native-unistyles"].includes(f), ); if (!hasBetterAuthCompatibleFrontend) { notes.auth.notes.push( - "Better-Auth with Convex requires TanStack Router, TanStack Start, Next.js, or React Native (NativeWind/Unistyles). Auth will be set to 'None'.", + "Better-Auth with Convex requires TanStack Router, TanStack Start, Next.js, or React Native (Bare/UniWind/Unistyles). Auth will be set to 'None'.", ); notes.backend.notes.push( "Convex backend with Better-Auth requires compatible frontend. Auth will be disabled.", @@ -1259,11 +1259,11 @@ export const getDisabledReason = ( ["tanstack-router", "tanstack-start", "next"].includes(f), ) || currentStack.nativeFrontend.some((f) => - ["native-nativewind", "native-unistyles"].includes(f), + ["native-bare", "native-uniwind", "native-unistyles"].includes(f), ); if (!hasBetterAuthCompatibleFrontend) { - return "Better-Auth with Convex requires TanStack Router, TanStack Start, Next.js, or React Native (NativeWind/Unistyles)."; + return "Better-Auth with Convex requires TanStack Router, TanStack Start, Next.js, or React Native (Bare/UniWind/Unistyles)."; } } } @@ -1351,11 +1351,11 @@ export const getDisabledReason = ( ), ) || finalStack.nativeFrontend.some((f) => - ["native-nativewind", "native-unistyles"].includes(f), + ["native-bare", "native-uniwind", "native-unistyles"].includes(f), ); if (!hasClerkCompatibleFrontend) { - return "Clerk requires TanStack Router, React Router, TanStack Start, Next.js, or React Native frontend."; + return "Clerk requires TanStack Router, React Router, TanStack Start, Next.js, or React Native (Bare/UniWind/Unistyles) frontend."; } } @@ -1366,11 +1366,11 @@ export const getDisabledReason = ( ["tanstack-router", "tanstack-start", "next"].includes(f), ) || finalStack.nativeFrontend.some((f) => - ["native-nativewind", "native-unistyles"].includes(f), + ["native-bare", "native-uniwind", "native-unistyles"].includes(f), ); if (!hasBetterAuthCompatibleFrontend) { - return "Better-Auth with Convex requires TanStack Router, TanStack Start, Next.js, or React Native (NativeWind/Unistyles)."; + return "Better-Auth with Convex requires TanStack Router, TanStack Start, Next.js, or React Native (Bare/UniWind/Unistyles)."; } } } diff --git a/apps/web/src/lib/constant.ts b/apps/web/src/lib/constant.ts index 678e34a8f..73197b95d 100644 --- a/apps/web/src/lib/constant.ts +++ b/apps/web/src/lib/constant.ts @@ -107,9 +107,18 @@ export const TECH_OPTIONS: Record< ], nativeFrontend: [ { - id: "native-nativewind", - name: "React Native + NativeWind", - description: "Expo with NativeWind (Tailwind)", + id: "native-bare", + name: "React Native + Bare", + description: "Expo with StyleSheet (no styling library)", + icon: `${ICON_BASE_URL}/expo.svg`, + color: "from-blue-400 to-blue-600", + className: "invert-0 dark:invert", + default: true, + }, + { + id: "native-uniwind", + name: "React Native + Uniwind", + description: "Expo with Uniwind (Tailwind CSS for React Native)", icon: `${ICON_BASE_URL}/expo.svg`, color: "from-purple-400 to-purple-600", className: "invert-0 dark:invert", @@ -118,7 +127,7 @@ export const TECH_OPTIONS: Record< { id: "native-unistyles", name: "React Native + Unistyles", - description: "Expo with Unistyles", + description: "Expo with Unistyles (type-safe styling)", icon: `${ICON_BASE_URL}/expo.svg`, color: "from-pink-400 to-pink-600", className: "invert-0 dark:invert", @@ -728,7 +737,7 @@ export const PRESET_TEMPLATES = [ stack: { projectName: "my-better-t-app", webFrontend: ["none"], - nativeFrontend: ["native-nativewind"], + nativeFrontend: ["native-bare"], runtime: "bun", backend: "hono", database: "sqlite", @@ -780,7 +789,7 @@ export const PRESET_TEMPLATES = [ stack: { projectName: "my-better-t-app", webFrontend: ["tanstack-router"], - nativeFrontend: ["native-nativewind"], + nativeFrontend: ["native-bare"], runtime: "bun", backend: "hono", database: "sqlite", From 4281aa8c027f739761c576ed83dfd03cd65424d9 Mon Sep 17 00:00:00 2001 From: Aman Varshney Date: Mon, 10 Nov 2025 00:16:32 +0530 Subject: [PATCH 5/5] update --- apps/cli/src/constants.ts | 2 +- .../native/bare/app/(drawer)/todos.tsx.hbs | 535 ++++++++++-------- .../bare/components/header-button.tsx.hbs | 6 +- .../stack-configuration-charts.tsx | 16 +- .../app/(home)/analytics/_components/types.ts | 10 +- 5 files changed, 333 insertions(+), 236 deletions(-) diff --git a/apps/cli/src/constants.ts b/apps/cli/src/constants.ts index 86dda0eab..8ef86bb4c 100644 --- a/apps/cli/src/constants.ts +++ b/apps/cli/src/constants.ts @@ -155,7 +155,7 @@ export const dependencyVersionMap = { "@sveltejs/adapter-cloudflare": "^7.2.1", "@cloudflare/workers-types": "^4.20250822.0", - alchemy: "^0.75.1", + alchemy: "^0.77.0", dotenv: "^17.2.2", tsdown: "^0.15.5", diff --git a/apps/cli/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs b/apps/cli/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs index d05a563b6..11aba1ec6 100644 --- a/apps/cli/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs +++ b/apps/cli/templates/examples/todo/native/bare/app/(drawer)/todos.tsx.hbs @@ -22,10 +22,10 @@ import { useColorScheme } from "@/lib/use-color-scheme"; import { NAV_THEME } from "@/lib/constants"; {{#unless (eq backend "convex")}} {{#if (eq api "orpc")}} - import { orpc } from "@/utils/orpc"; +import { orpc } from "@/utils/orpc"; {{/if}} {{#if (eq api "trpc")}} - import { trpc } from "@/utils/trpc"; +import { trpc } from "@/utils/trpc"; {{/if}} {{/unless}} @@ -33,109 +33,122 @@ export default function TodosScreen() { const { colorScheme } = useColorScheme(); const theme = colorScheme === "dark" ? NAV_THEME.dark : NAV_THEME.light; const [newTodoText, setNewTodoText] = useState(""); - + {{#if (eq backend "convex")}} - const todos = useQuery(api.todos.getAll); - const createTodoMutation = useMutation(api.todos.create); - const toggleTodoMutation = useMutation(api.todos.toggle); - const deleteTodoMutation = useMutation(api.todos.deleteTodo); + const todos = useQuery(api.todos.getAll); + const createTodoMutation = useMutation(api.todos.create); + const toggleTodoMutation = useMutation(api.todos.toggle); + const deleteTodoMutation = useMutation(api.todos.deleteTodo); + + async function handleAddTodo() { + const text = newTodoText.trim(); + if (!text) return; + await createTodoMutation({ text }); + setNewTodoText(""); + } + + function handleToggleTodo(id: Id<"todos">, currentCompleted: boolean) { + toggleTodoMutation({ id, completed: !currentCompleted }); + } + + function handleDeleteTodo(id: Id<"todos">) { + Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: () => deleteTodoMutation({ id }), + }, + ]); + } + + const isLoading = !todos; + const completedCount = todos?.filter((t) => t.completed).length || 0; + const totalCount = todos?.length || 0; {{else}} {{#if (eq api "orpc")}} - const todos = useQuery(orpc.todo.getAll.queryOptions()); - const createMutation = useMutation(orpc.todo.create.mutationOptions({ - onSuccess: () => { - todos.refetch(); - setNewTodoText(""); - }, - })); - const toggleMutation = useMutation(orpc.todo.toggle.mutationOptions({ - onSuccess: () => { - todos.refetch(); - }, - })); - const deleteMutation = useMutation(orpc.todo.delete.mutationOptions({ - onSuccess: () => { - todos.refetch(); - }, - })); + const todos = useQuery(orpc.todo.getAll.queryOptions()); + const createMutation = useMutation( + orpc.todo.create.mutationOptions({ + onSuccess: () => { + todos.refetch(); + setNewTodoText(""); + }, + }) + ); + const toggleMutation = useMutation( + orpc.todo.toggle.mutationOptions({ + onSuccess: () => { + todos.refetch(); + }, + }) + ); + const deleteMutation = useMutation( + orpc.todo.delete.mutationOptions({ + onSuccess: () => { + todos.refetch(); + }, + }) + ); {{/if}} {{#if (eq api "trpc")}} - const todos = useQuery(trpc.todo.getAll.queryOptions()); - const createMutation = useMutation(trpc.todo.create.mutationOptions({ - onSuccess: () => { - todos.refetch(); - setNewTodoText(""); - }, - })); - const toggleMutation = useMutation(trpc.todo.toggle.mutationOptions({ - onSuccess: () => { - todos.refetch(); - }, - })); - const deleteMutation = useMutation(trpc.todo.delete.mutationOptions({ - onSuccess: () => { - todos.refetch(); - }, - })); + const todos = useQuery(trpc.todo.getAll.queryOptions()); + const createMutation = useMutation( + trpc.todo.create.mutationOptions({ + onSuccess: () => { + todos.refetch(); + setNewTodoText(""); + }, + }) + ); + const toggleMutation = useMutation( + trpc.todo.toggle.mutationOptions({ + onSuccess: () => { + todos.refetch(); + }, + }) + ); + const deleteMutation = useMutation( + trpc.todo.delete.mutationOptions({ + onSuccess: () => { + todos.refetch(); + }, + }) + ); {{/if}} - {{/if}} - - {{#if (eq backend "convex")}} - async function handleAddTodo() { - const text = newTodoText.trim(); - if (!text) return; - await createTodoMutation({ text }); - setNewTodoText(""); - } - - function handleToggleTodo(id: Id<"todos">, currentCompleted: boolean) { - toggleTodoMutation({ id, completed: !currentCompleted }); - } - function handleDeleteTodo(id: Id<"todos">) { - Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [ - { text: "Cancel", style: "cancel" }, - { - text: "Delete", - style: "destructive", - onPress: () => deleteTodoMutation({ id }), - }, - ]); + function handleAddTodo() { + if (newTodoText.trim()) { + createMutation.mutate({ text: newTodoText }); } + } - const isLoading = !todos; - const completedCount = todos?.filter((t) => t.completed).length || 0; - const totalCount = todos?.length || 0; - {{else}} - function handleAddTodo() { - if (newTodoText.trim()) { - createMutation.mutate({ text: newTodoText }); - } - } + function handleToggleTodo(id: number, completed: boolean) { + toggleMutation.mutate({ id, completed: !completed }); + } - function handleToggleTodo(id: number, completed: boolean) { - toggleMutation.mutate({ id, completed: !completed }); - } - - function handleDeleteTodo(id: number) { - Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [ - { text: "Cancel", style: "cancel" }, - { - text: "Delete", - style: "destructive", - onPress: () => deleteMutation.mutate({ id }), - }, - ]); - } + function handleDeleteTodo(id: number) { + Alert.alert("Delete Todo", "Are you sure you want to delete this todo?", [ + { text: "Cancel", style: "cancel" }, + { + text: "Delete", + style: "destructive", + onPress: () => deleteMutation.mutate({ id }), + }, + ]); + } - const isLoading = todos?.isLoading; - const completedCount = todos?.data?.filter((t) => t.completed).length || 0; - const totalCount = todos?.data?.length || 0; + const isLoading = todos?.isLoading; + const completedCount = todos?.data?.filter((t) => t.completed).length || 0; + const totalCount = todos?.data?.length || 0; {{/if}} return ( - + @@ -150,8 +163,12 @@ export default function TodosScreen() { )} - - + - + disabled={!newTodoText.trim()} + style={[ + styles.addButton, + { + backgroundColor: !newTodoText.trim() + ? theme.border + : theme.primary, + opacity: !newTodoText.trim() ? 0.5 : 1, + }, + ]} + > + {{else}} - disabled={createMutation.isPending || !newTodoText.trim()} - style={[ - styles.addButton, - { - backgroundColor: (createMutation.isPending || !newTodoText.trim()) ? theme.border : theme.primary, - opacity: (createMutation.isPending || !newTodoText.trim()) ? 0.5 : 1, - }, - ]} - > - {createMutation.isPending ? ( - - ) : ( - - )} + disabled={createMutation.isPending || !newTodoText.trim()} + style={[ + styles.addButton, + { + backgroundColor: + createMutation.isPending || !newTodoText.trim() + ? theme.border + : theme.primary, + opacity: + createMutation.isPending || !newTodoText.trim() ? 0.5 : 1, + }, + ]} + > + {createMutation.isPending ? ( + + ) : ( + + )} {{/if}} {{#if (eq backend "convex")}} - {isLoading && ( - - - - Loading todos... - - - )} + {isLoading && ( + + + + Loading todos... + + + )} - {todos && todos.length === 0 && !isLoading && ( - - - - No todos yet - - - Add your first task to get started! - - - )} + {todos && todos.length === 0 && !isLoading && ( + + + + No todos yet + + + Add your first task to get started! + + + )} - {todos && todos.length > 0 && ( - - {todos.map((todo) => ( - - - handleToggleTodo(todo._id, todo.completed)} - style={[styles.checkbox, { borderColor: theme.border }]} - > - {todo.completed && ( - - )} - - - 0 && ( + + {todos.map((todo) => ( + + + handleToggleTodo(todo._id, todo.completed)} + style={[styles.checkbox, { borderColor: theme.border }]} + > + {todo.completed && ( + + )} + + + - {todo.text} - - - handleDeleteTodo(todo._id)} - style=\{{ styles.deleteButton }} + todo.completed && { + textDecorationLine: "line-through", + opacity: 0.5, + }, + ]} > - - + {todo.text} + + handleDeleteTodo(todo._id)} + style={styles.deleteButton} + > + + - ))} - - )} + + ))} + + )} {{else}} - {isLoading && ( - - - - Loading todos... - - - )} + {isLoading && ( + + + + Loading todos... + + + )} - {todos?.data && todos.data.length === 0 && !isLoading && ( - - - - No todos yet - - - Add your first task to get started! - - - )} + {todos?.data && todos.data.length === 0 && !isLoading && ( + + + + No todos yet + + + Add your first task to get started! + + + )} - {todos?.data && todos.data.length > 0 && ( - - {todos.data.map((todo) => ( - - - handleToggleTodo(todo.id, todo.completed)} - style={[styles.checkbox, { borderColor: theme.border }]} - > - {todo.completed && ( - - )} - - - 0 && ( + + {todos.data.map((todo) => ( + + + handleToggleTodo(todo.id, todo.completed)} + style={[styles.checkbox, { borderColor: theme.border }]} + > + {todo.completed && ( + + )} + + + - {todo.text} - - - handleDeleteTodo(todo.id)} - style={styles.deleteButton} + todo.completed && { + textDecorationLine: "line-through", + opacity: 0.5, + }, + ]} > - - + {todo.text} + + handleDeleteTodo(todo.id)} + style={styles.deleteButton} + > + + - ))} - - )} + + ))} + + )} {{/if}} @@ -426,5 +518,4 @@ const styles = StyleSheet.create({ deleteButton: { padding: 8, }, -}); - +}); \ No newline at end of file diff --git a/apps/cli/templates/frontend/native/bare/components/header-button.tsx.hbs b/apps/cli/templates/frontend/native/bare/components/header-button.tsx.hbs index 397539807..88f433f83 100644 --- a/apps/cli/templates/frontend/native/bare/components/header-button.tsx.hbs +++ b/apps/cli/templates/frontend/native/bare/components/header-button.tsx.hbs @@ -1,11 +1,11 @@ import FontAwesome from "@expo/vector-icons/FontAwesome"; import { forwardRef } from "react"; -import { Pressable, StyleSheet } from "react-native"; +import { Pressable, StyleSheet, View } from "react-native"; import { useColorScheme } from "@/lib/use-color-scheme"; import { NAV_THEME } from "@/lib/constants"; export const HeaderButton = forwardRef< - typeof Pressable, + View, { onPress?: () => void } >(({ onPress }, ref) => { const { colorScheme } = useColorScheme(); @@ -19,7 +19,7 @@ export const HeaderButton = forwardRef< styles.button, { backgroundColor: pressed - ? theme.secondary + ? theme.background : theme.card, }, ]} diff --git a/apps/web/src/app/(home)/analytics/_components/stack-configuration-charts.tsx b/apps/web/src/app/(home)/analytics/_components/stack-configuration-charts.tsx index 4e1bf80f3..8c22bb61a 100644 --- a/apps/web/src/app/(home)/analytics/_components/stack-configuration-charts.tsx +++ b/apps/web/src/app/(home)/analytics/_components/stack-configuration-charts.tsx @@ -151,15 +151,17 @@ export function StackConfigurationCharts({ ? "hsl(var(--chart-4))" : entry.name === "nuxt" ? "hsl(var(--chart-5))" - : entry.name === "native-nativewind" + : entry.name === "native-bare" ? "hsl(var(--chart-6))" - : entry.name === "native-unistyles" + : entry.name === "native-uniwind" ? "hsl(var(--chart-7))" - : entry.name === "svelte" - ? "hsl(var(--chart-3))" - : entry.name === "solid" - ? "hsl(var(--chart-4))" - : "hsl(var(--chart-7))" + : entry.name === "native-unistyles" + ? "hsl(var(--chart-1))" + : entry.name === "svelte" + ? "hsl(var(--chart-3))" + : entry.name === "solid" + ? "hsl(var(--chart-4))" + : "hsl(var(--chart-7))" } /> ))} diff --git a/apps/web/src/app/(home)/analytics/_components/types.ts b/apps/web/src/app/(home)/analytics/_components/types.ts index 9428cc1b1..35efd910b 100644 --- a/apps/web/src/app/(home)/analytics/_components/types.ts +++ b/apps/web/src/app/(home)/analytics/_components/types.ts @@ -240,13 +240,17 @@ export const frontendConfig = { label: "Nuxt", color: "hsl(var(--chart-5))", }, - "native-nativewind": { - label: "Expo NativeWind", + "native-bare": { + label: "Expo Bare", color: "hsl(var(--chart-6))", }, + "native-uniwind": { + label: "Expo Uniwind", + color: "hsl(var(--chart-7))", + }, "native-unistyles": { label: "Expo Unistyles", - color: "hsl(var(--chart-7))", + color: "hsl(var(--chart-1))", }, svelte: { label: "Svelte",